.NET 允許 C#、C++/CLI、Visual Basic .NET、F# 等語言編寫的組件無縫互呼。關鍵在於:所有語言編譯為相容 CTS 的 IL,遵循 CLS 規則的公開 API 可被所有 CLS 相容語言消費。本文以「字典組件」為例,展示 C++/CLI、VB.NET、C# 三語言實作與 C# 客戶端統一消費,並涵蓋 P/Invoke、COM Interop、組件版本重導向、SafeHandle 進階主題。
DICTVC.dll)/clr 編譯選項)。Microsoft.VisualStudio.CppCLI.Support)。public ref class(受控類別)、property、gcnew、array<T>^、String^。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; }
}
};
}
DICTVC.dll:混合模式組件(含 IL + 原生機器碼),需對應平台(x86/x64/ARM64)部署。
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 專案建置。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;
}
}
// 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
// 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 回收委派需保持參考。
// 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);
Marshal.ReleaseComObject 或 Marshal.FinalReleaseComObject 釋放,避免 COM 物件洩漏。Embed Interop Types = True(預設)將必要型別嵌入組件,免部署 Interop DLL。
sn -k key.snk、[assembly: AssemblyKeyFile("key.snk")])才支援 GAC/並行執行/重導向。AssemblyLoadContext 隔離載入不同版本同名組件(外掛架構)。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 |
[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,即能在新舊技術棧間構建穩固橋樑。