首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 呼叫 C++/VB 組件實戰:跨語言互操作、P/Invoke、COM Interop 與組件版本管理

     編輯:關於C#

前言:.NET 多語言互操作的基石——共同型別系統 (CTS) 與共同語言規格 (CLS)

.NET 允許 C#、C++/CLI、Visual Basic .NET、F# 等語言編寫的組件無縫互呼。關鍵在於:所有語言編譯為相容 CTS 的 IL,遵循 CLS 規則的公開 API 可被所有 CLS 相容語言消費。本文以「字典組件」為例,展示 C++/CLI、VB.NET、C# 三語言實作與 C# 客戶端統一消費,並涵蓋 P/Invoke、COM Interop、組件版本重導向、SafeHandle 進階主題。

一、C++/CLI 組件實作(DICTVC.dll)

1.1 專案設定

  • 專案類型:CLR 類別庫(/clr 編譯選項)。
  • 目標架構:.NET Framework 4.8 / .NET 6+(需 Microsoft.VisualStudio.CppCLI.Support)。
  • 關鍵語法:public ref class(受控類別)、property、gcnew、array<T>^、String^。

1.2 程式碼:DictionaryComponent.h/cpp


// DictionaryComponent.h
#pragma once
using namespace System;

namespace DICTVC {
    public ref class DictionaryComponent
    {
    private:
        String^ _languageName;
        array^ _availableLanguages;

    public:
        DictionaryComponent() {
            _languageName = nullptr;
            _availableLanguages = gcnew array(4);
            _availableLanguages[0] = "Chinese";
            _availableLanguages[1] = "English";
            _availableLanguages[2] = "German";
            _availableLanguages[3] = "French";
        }

        bool LoadLibrary(String^ language) {
            for (int i = 0; i < _availableLanguages->Length; i++) {
                if (language == _availableLanguages[i]) {
                    _languageName = language;
                    return true;
                }
            }
            return false;
        }

        void FreeLibrary() { _languageName = nullptr; }

        property String^ CurrentLibrary {
            String^ get() { return _languageName; }
        }
    };
}

1.3 編譯產出

DICTVC.dll:混合模式組件(含 IL + 原生機器碼),需對應平台(x86/x64/ARM64)部署。

二、VB.NET 組件實作(DICTVB.dll)


' DictionaryComponent.vb
Imports System

Namespace DICTVB
    Public Class DictionaryComponent
        Private _availableLanguages(3) As String
        Private _languageName As String

        Public Sub New()
            _availableLanguages(0) = "Chinese"
            _availableLanguages(1) = "English"
            _availableLanguages(2) = "Japanese"
            _availableLanguages(3) = "Korean"
            _languageName = Nothing
        End Sub

        Public Function LoadLibrary(ByVal language As String) As Boolean
            For i = 0 To 3
                If language = _availableLanguages(i) Then
                    _languageName = language
                    Return True
                End If
            Next
            Return False
        End Function

        Public Sub FreeLibrary()
            _languageName = Nothing
        End Sub

        Public ReadOnly Property CurrentLibrary As String
            Get
                Return _languageName
            End Get
        End Property
    End Class
End Namespace
  • 編譯:vbc /target:library /out:DICTVB.dll DictionaryComponent.vb 或 VS 專案建置。
  • 純 IL 組件,跨平台(Any CPU)。

三、C# 組件實作(DICTCS.dll)


// DictionaryComponent.cs
using System;

namespace DICTCS
{
    public class DictionaryComponent
    {
        private string _languageName;
        private readonly string[] _availableLanguages = { "Chinese", "English", "German", "French" };

        public bool LoadLibrary(string language)
        {
            foreach (var lang in _availableLanguages)
                if (language == lang) { _languageName = language; return true; }
            return false;
        }

        public void FreeLibrary() => _languageName = null;

        public string CurrentLibrary => _languageName;
    }
}

四、C# 客戶端統一消費三語言組件


// ClientCS.cs
using System;
using DICTVC;
using DICTCS;
using DICTVB;

class Program
{
    static void Main()
    {
        Console.Write("Input library language: ");
        string lang = Console.ReadLine()?.Trim() ?? "";

        // C# 組件
        var csDict = new DICTCS.DictionaryComponent();
        TestDict("C#", csDict, lang);

        // C++/CLI 組件
        var vcDict = new DICTVC.DictionaryComponent();
        TestDict("C++/CLI", vcDict, lang);

        // VB.NET 組件
        var vbDict = new DICTVB.DictionaryComponent();
        TestDict("VB.NET", vbDict, lang);
    }

    static void TestDict(string label, dynamic dict, string lang)
    {
        Console.WriteLine($"\nDictionary from {label} DictionaryComponent");
        if (dict.LoadLibrary(lang))
            Console.WriteLine("Library load successfully!");
        else
            Console.WriteLine("Library not found!");

        Console.WriteLine($"Current library: {dict.CurrentLibrary ?? "(none)"}");
        dict.FreeLibrary();
    }
}
  • dynamic 簡化跨組件呼叫(繞過編譯期型別檢查),或定義共用介面 IDictionaryComponent 實作強型別消費。
  • 編譯:csc /r:DICTCS.dll;DICTVB.dll;DICTVC.dll ClientCS.cs

五、進階互操作主題

5.1 P/Invoke:呼叫非受控 C++ DLL(非 C++/CLI)


// NativeCppLib.h (純 C++ DLL)
extern "C" __declspec(dllexport) int Add(int a, int b);

// C# 簽章
[DllImport("NativeCppLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Add(int a, int b);
  • CallingConvention.Cdecl:C/C++ 預設呼叫慣例(呼叫者清理堆疊)。
  • 字串封送:[MarshalAs(UnmanagedType.LPStr)] (ANSI)、LPWStr (Unicode)、BStr (COM)。
  • 結構體封送:[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]。
  • 回呼委派:delegate void Callback(int progress); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)],防止 GC 回收委派需保持參考。

5.2 COM Interop:呼叫傳統 COM 元件


// 1. 參考 COM 類型庫 → VS 產生 Interop.XXX.dll (RCW)
// 2. 使用
var excel = new Microsoft.Office.Interop.Excel.Application();
var wb = excel.Workbooks.Add();
wb.SaveAs("test.xlsx");
Marshal.ReleaseComObject(wb);
excel.Quit();
Marshal.ReleaseComObject(excel);
  • RCW (Runtime Callable Wrapper) 管理 COM 物件生命週期(參考計數)。
  • 必須顯式 Marshal.ReleaseComObject 或 Marshal.FinalReleaseComObject 釋放,避免 COM 物件洩漏。
  • Embed Interop Types = True(預設)將必要型別嵌入組件,免部署 Interop DLL。

5.3 組件版本重導向與並行執行




  
    
      
      
    
  

  • 強式名稱組件(sn -k key.snk、[assembly: AssemblyKeyFile("key.snk")])才支援 GAC/並行執行/重導向。
  • .NET Core / .NET 5+:AssemblyLoadContext 隔離載入不同版本同名組件(外掛架構)。

5.4 SafeHandle 封裝非受控資源(替代 IntPtr + Finalize)


internal sealed class SafeDictionaryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    private SafeDictionaryHandle() : base(true) { }
    public static SafeDictionaryHandle Create(string path) => 
        new SafeDictionaryHandle { handle = Native.LoadDictionary(path) };
    protected override bool ReleaseHandle() => Native.FreeDictionary(handle);
}

// C# 使用
using (var handle = SafeDictionaryHandle.Create("dict.dat")) {
    Native.Query(handle, "key");
} // 自動釋放,甚至 AppDomain 卸載也安全

六、除錯與診斷跨語言問題

問題原因排查工具
FileNotFoundException 載入 C++/CLI DLL缺少 VC++ Runtime、位元數不符(x86 vs x64)、相依原生 DLL 缺失fuslogvw (Fusion Log)、Dependency Walker、Process Monitor
InvalidCastException COM 介面查詢失敗介面 ID 不符、RCW 型別庫版本不匹配OleView、RegAsm /tlb 重新產生 Interop
P/Invoke 參數封送錯誤CharSet、StructLayout.Pack、MarshalAs 不匹配pinvoke.net 參考簽章、啟用 SetLastError + Marshal.GetLastWin32Error
記憶體洩漏未釋放 SafeHandle/COM 物件、委派被 GC 回收導致回呼失效dotnet-gcdump、PerfView、DebugDiag

七、現代替代方案:gRPC / HTTP API / Source Generators

  • gRPC:跨語言、跨程序、跨機器、IDL 定義契約、效能高(HTTP/2 + Protobuf)。
  • ASP.NET Core Web API:REST/JSON、通用、防火牆友善。
  • Source Generators:編譯期產生 P/Invoke 簽章、COM 包裝器、介面 Proxy,消除手寫錯誤。
  • LibraryImport (C# 11+):[LibraryImport("native.dll")] 部分取代 DllImport,支援原生 AOT、自動產生封送樁碼。

結語

.NET 多語言互操作建立在 CTS/CLS 之上,C++/CLI、VB.NET、C# 組件可透過共用介面或 dynamic 無縫呼叫。面對非受控 C++ DLL 需 P/Invoke、傳統 COM 需 RCW/CCW、版本衝突需強式名稱+重導向。現代架構傾向 gRPC/Web API 跨邊界通訊,但既有資產遷移仍大量依賴 Interop 技術。掌握封送規則、SafeHandle、AssemblyLoadContext、Source Generators,即能在新舊技術棧間構建穩固橋樑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved