class CFoo {
CMember m_obj;
public:
CFoo(const CFoo& other) { *this = other; } // 錯誤!
CFoo& operator=(const CFoo& rhs) { m_obj = rhs.m_obj; return *this; }
};
後果:m_obj 被**初始化兩次** —— 先默認構造,再賦值運算符覆蓋。若 CMember 構造昂貴(分配記憶體、打開句柄),效能損失巨大;若 CMember 含 const 或引用成員,甚至編譯報錯。
class CFoo {
CMember m_obj;
BaseClass m_base;
public:
// 拷貝構造:逐成員拷貝構造
CFoo(const CFoo& rhs) : m_obj(rhs.m_obj), m_base(rhs.m_base) {}
// 賦值運算符:逐成員賦值,處理自賦值、強異常安全
CFoo& operator=(const CFoo& rhs) {
if (this != &rhs) { // 自賦值檢查
m_obj = rhs.m_obj;
m_base = rhs.m_base;
}
return *this;
}
};
private void CopyFrom(const CFoo&) 供兩者調用,但**拷貝構造仍需在初始化列表完成成員構造**,再調用輔助函數做額外工作。
CFoo(CFoo&& rhs) noexcept : m_obj(std::move(rhs.m_obj)), m_base(std::move(rhs.m_base)) {}
CFoo& operator=(CFoo&& rhs) noexcept {
if (this != &rhs) {
m_obj = std::move(rhs.m_obj);
m_base = std::move(rhs.m_base);
}
return *this;
}
| 路徑 | 適用場景 | 核心技術 | 優點 | 缺點 |
|---|---|---|---|---|
| C++/CLI 托管包裝器 | 大量 C++ 類庫需暴露給 .NET,面向對象映射自然 | #using <mscorlib.dll>, ref class, gcnew | 類級封裝、自動記憶體管理、支援屬性/事件/泛型、Visual Studio 原生調試 | 需編譯混合模式 DLL、僅限 Windows、C++/CLI 語法學習成本 |
| P/Invoke (DllImport) | 少量 C 風格函數、系統 API、第三方 C SDK | [DllImport], MarshalAs, IntPtr | 純 C#、跨平台 (Core/5+)、無額外 DLL、效能高 | 手寫簽名易錯、結構體對齊難、類/重載/異常映射麻煩 |
| COM 互操作 | 現有 COM 組件、Office 自動化、系統 Shell 擴展 | ComImport, Guid, InterfaceType | 語言中立、版本控制、進程外激活 | 註冊部署複雜、參數封送開銷大、調試困難 |
// NativeLib.h
#pragma once
class __declspec(dllexport) CPerson {
public:
CPerson(const char* name);
~CPerson();
const char* GetName() const;
void SetName(const char* name);
private:
char* m_name;
};
// NativeLib.cpp
#include "NativeLib.h"
#include
CPerson::CPerson(const char* name) { m_name = _strdup(name); }
CPerson::~CPerson() { free(m_name); }
const char* CPerson::GetName() const { return m_name; }
void CPerson::SetName(const char* name) { free(m_name); m_name = _strdup(name); }
// ManagedWrapper.cpp
#include "NativeLib.h"
#using
#using
using namespace System;
using namespace System::Runtime::InteropServices;
namespace ManagedLib {
public ref class Person {
private:
CPerson* _native; // 原生指針
public:
Person(String^ name) {
IntPtr ptr = Marshal::StringToHGlobalAnsi(name);
_native = new CPerson(static_cast(ptr.ToPointer()));
Marshal::FreeHGlobal(ptr);
}
~Person() { this->!Person(); }
!Person() { delete _native; _native = nullptr; } // 終結器釋放原生資源
property String^ Name {
String^ get() {
const char* nativeName = _native->GetName();
return Marshal::PtrToStringAnsi(IntPtr(const_cast(nativeName)));
}
void set(String^ value) {
IntPtr ptr = Marshal::StringToHGlobalAnsi(value);
_native->SetName(static_cast(ptr.ToPointer()));
Marshal::FreeHGlobal(ptr);
}
}
};
}
編譯:cl /clr /LD ManagedWrapper.cpp NativeLib.cpp /link /DLL /OUT:ManagedLib.dll
using ManagedLib;
var p = new Person("Fred");
Console.WriteLine(p.Name); // Fred
p.Name = "Freddie";
Console.WriteLine(p.Name); // Freddie
public static class NativeMethods {
// 字串:CharSet.Auto 根據平台自動選 Unicode/ANSI
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int type);
// 結構體:Sequential 佈局,Pack=1 防止對齊差異
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct LOGFONT {
public int lfHeight, lfWidth, lfEscapement, lfOrientation, lfWeight;
public byte lfItalic, lfUnderline, lfStrikeOut, lfCharSet, lfOutPrecision,
lfClipPrecision, lfQuality, lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string lfFaceName;
}
// 回調/委托:UnmanagedFunctionPointer 必須匹配調用約定
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
// 陣列:In/Out 屬性控制封送方向
[DllImport("mylib.dll")]
public static extern void ProcessData([In, Out] int[] data, int count);
}
| 坑點 | 現象 | 解決 |
|---|---|---|
| 調用約定不匹配 | ESP not properly saved、堆棧破壞 | C++ 默認 __cdecl/__stdcall/__thiscall,C# 默認 StdCall,[DllImport(CallingConvention=...)] 顯式指定 |
| 字串編碼 | 中文亂碼、截斷 | 統一用 CharSet.Unicode + MarshalAs(UnmanagedType.LPWStr),或 CharSet.Ansi + LPSTR |
| 結構體對齊 | 欄位位移錯、讀寫錯誤記憶體 | [StructLayout(LayoutKind.Sequential, Pack=...)],用 sizeof 對照原生 sizeof |
| 指針固定 | GC 移動陣列/字串導致原生指針失效 | GCHandle.Alloc(obj, GCHandleType.Pinned) 或 fixed (byte* p = arr),務必在 try/finally 釋放 |
| 異常跨邊界 | C++ 拋出 exception 導致 CLR 崩潰 | C++ 邊界捕獲所有異常轉為 HRESULT/錯誤碼,C++/CLI 用 try/catch(...) 包裝轉 Exception |
| 記憶體洩漏 | 原生 new 無 delete、C# 忘記 Dispose | C++/CLI 實現 IDisposable + 終結器雙重保障;P/Invoke 明確文檔誰負責釋放 |
// .NET 7+ LibraryImport (Source Generator, 更安全、支援 NativeAOT)
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
public static partial int ProcessData(int[] data, int count);
// 函數指標替代委托
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
public static int Callback(IntPtr hWnd, IntPtr lParam) => 0;
LibraryImport 編譯期生成封送代碼,支援 NativeAOT 免運行時發布;UnmanagedCallersOnly 讓靜態方法直接作為回調傳給原生碼,無委托開銷。C++ 拷貝構造**必須**用初始化列表逐成員拷貝構造,賦值運算符處理自賦值並逐成員賦值,二者職責不同不可混用。C# 呼叫 C++ 首選 **C++/CLI 托管包裝器**(類庫級),次選 **P/Invoke**(函數級),現代專案可評估 LibraryImport + NativeAOT。核心原則:**邊界清晰、所有權明確、封送可控、異常不跨邊界**。