使用C++包装器访问C# .DLL属性的方法
我之前也处理过类似的C# DLL给C调用的场景,核心问题在于非托管C没法直接识别托管C#的属性系统——毕竟两者的类型模型完全不同。下面给你两种可行的方案,以及针对不同权限属性的处理方式:
核心思路
非托管C++无法直接访问C#的属性成员,必须通过将属性的get/set逻辑包装成公开方法的方式来暴露。同时要确保C#的类和方法对非托管代码可见,还要注意托管与非托管类型的转换匹配。
方案1:使用COM互操作(推荐面向对象场景)
这种方式适合需要实例化C#类、调用成员方法和属性的场景,通过将C#类标记为COM可见,让C++可以像使用COM组件一样访问它。
C# DLL代码实现
首先给你的类添加COM可见标记,同时为不同权限的属性编写包装方法:
using System; using System.Runtime.InteropServices; // 生成自己的GUID:可以用VS的工具 -> 创建GUID,选第4种格式 [ComVisible(true)] [Guid("12345678-1234-1234-1234-123456789ABC")] public class MyCppWrapper { // 只读属性 public int ReadOnlyId { get; } = 42; // 读写属性 public string ConfigName { get; set; } = "DefaultConfig"; // 受保护写入的属性(set为protected) public int InternalCounter { get; protected set; } = 0; // 你已经实现的业务函数 public void ExecuteBusinessLogic() { Console.WriteLine("Business logic executed!"); } // -------------------------- // 包装属性的访问方法 // -------------------------- // 只读属性:只暴露Get方法 public int GetReadOnlyId() { return ReadOnlyId; } // 读写属性:暴露Get和Set方法 public string GetConfigName() { return ConfigName; } public void SetConfigName(string newName) { ConfigName = newName; } // 受保护写入属性:通过公开方法间接设置(因为C++无法访问protected的set) public int GetInternalCounter() { return InternalCounter; } public void SetInternalCounter(int newValue) { // 这里可以添加权限校验逻辑,比如只允许特定条件下修改 InternalCounter = newValue; } }
然后在C#项目的生成选项里勾选“为COM互操作注册”,编译生成DLL。
C++调用代码示例
C++通过#import指令加载C#生成的类型库,然后实例化类并调用方法:
#include <iostream> #import "MyCppWrapper.dll" no_namespace int main() { // 初始化COM环境 CoInitialize(NULL); MyCppWrapperPtr pWrapper; HRESULT hr = pWrapper.CreateInstance(__uuidof(MyCppWrapper)); if (SUCCEEDED(hr)) { // 调用已实现的业务函数 pWrapper->ExecuteBusinessLogic(); // 获取只读属性 int id = pWrapper->GetReadOnlyId(); std::cout << "只读属性ReadOnlyId的值:" << id << std::endl; // 读写属性的获取与修改 _bstr_t configName = pWrapper->GetConfigName(); std::cout << "初始ConfigName:" << (const char*)configName << std::endl; pWrapper->SetConfigName(_bstr_t("UpdatedByCpp")); configName = pWrapper->GetConfigName(); std::cout << "修改后的ConfigName:" << (const char*)configName << std::endl; // 受保护写入属性的获取与修改 int counter = pWrapper->GetInternalCounter(); std::cout << "初始InternalCounter:" << counter << std::endl; pWrapper->SetInternalCounter(10); counter = pWrapper->GetInternalCounter(); std::cout << "修改后的InternalCounter:" << counter << std::endl; } // 释放COM环境 CoUninitialize(); return 0; }
方案2:使用P/Invoke(适合轻量静态/句柄式调用)
如果不需要完整的面向对象实例,或者想更轻量化地调用,可以用P/Invoke方式,通过导出函数传递实例句柄来访问属性。这种方式需要借助UnmanagedExports NuGet包来让C#导出函数给C++调用。
C# DLL代码实现
首先安装UnmanagedExports NuGet包,然后编写导出函数:
using System; using System.Runtime.InteropServices; public class MyCppWrapper { // 同方案1的属性定义 public int ReadOnlyId { get; } = 42; public string ConfigName { get; set; } = "DefaultConfig"; public int InternalCounter { get; protected set; } = 0; public void ExecuteBusinessLogic() { Console.WriteLine("Business logic executed!"); } // -------------------------- // 导出给C++的函数 // -------------------------- // 创建实例并返回句柄 [DllExport("CreateWrapperInstance", CallingConvention = CallingConvention.Cdecl)] public static IntPtr CreateInstance() { return Marshal.GetIUnknownForObject(new MyCppWrapper()); } // 释放实例 [DllExport("DestroyWrapperInstance", CallingConvention = CallingConvention.Cdecl)] public static void DestroyInstance(IntPtr instancePtr) { Marshal.Release(instancePtr); } // 包装业务函数 [DllExport("ExecuteBusinessLogic", CallingConvention = CallingConvention.Cdecl)] public static void ExecuteLogic(IntPtr instancePtr) { var instance = Marshal.GetObjectForIUnknown(instancePtr) as MyCppWrapper; instance?.ExecuteBusinessLogic(); } // 包装只读属性 [DllExport("GetReadOnlyId", CallingConvention = CallingConvention.Cdecl)] public static int GetId(IntPtr instancePtr) { var instance = Marshal.GetObjectForIUnknown(instancePtr) as MyCppWrapper; return instance?.ReadOnlyId ?? 0; } // 包装读写属性 [DllExport("GetConfigName", CallingConvention = CallingConvention.Cdecl)] public static IntPtr GetConfig(IntPtr instancePtr) { var instance = Marshal.GetObjectForIUnknown(instancePtr) as MyCppWrapper; return Marshal.StringToHGlobalAnsi(instance?.ConfigName ?? ""); } [DllExport("SetConfigName", CallingConvention = CallingConvention.Cdecl)] public static void SetConfig(IntPtr instancePtr, string newName) { var instance = Marshal.GetObjectForIUnknown(instancePtr) as MyCppWrapper; if (instance != null) instance.ConfigName = newName; } // 包装受保护写入属性 [DllExport("GetInternalCounter", CallingConvention = CallingConvention.Cdecl)] public static int GetCounter(IntPtr instancePtr) { var instance = Marshal.GetObjectForIUnknown(instancePtr) as MyCppWrapper; return instance?.InternalCounter ?? 0; } [DllExport("SetInternalCounter", CallingConvention = CallingConvention.Cdecl)] public static void SetCounter(IntPtr instancePtr, int newValue) { var instance = Marshal.GetObjectForIUnknown(instancePtr) as MyCppWrapper; if (instance != null) instance.InternalCounter = newValue; } }
C++调用代码示例
C++通过加载DLL并获取导出函数地址来调用:
#include <iostream> #include <windows.h> // 定义函数指针类型 typedef void* (*CreateInstanceFunc)(); typedef void (*DestroyInstanceFunc)(void*); typedef void (*ExecuteLogicFunc)(void*); typedef int (*GetIdFunc)(void*); typedef char* (*GetConfigFunc)(void*); typedef void (*SetConfigFunc)(void*, const char*); typedef int (*GetCounterFunc)(void*); typedef void (*SetCounterFunc)(void*, int); int main() { HMODULE hDll = LoadLibraryA("MyCppWrapper.dll"); if (!hDll) { std::cout << "加载DLL失败!" << std::endl; return 1; } // 获取导出函数地址 CreateInstanceFunc createFunc = (CreateInstanceFunc)GetProcAddress(hDll, "CreateWrapperInstance"); DestroyInstanceFunc destroyFunc = (DestroyInstanceFunc)GetProcAddress(hDll, "DestroyWrapperInstance"); ExecuteLogicFunc execFunc = (ExecuteLogicFunc)GetProcAddress(hDll, "ExecuteBusinessLogic"); GetIdFunc getIdFunc = (GetIdFunc)GetProcAddress(hDll, "GetReadOnlyId"); GetConfigFunc getConfigFunc = (GetConfigFunc)GetProcAddress(hDll, "GetConfigName"); SetConfigFunc setConfigFunc = (SetConfigFunc)GetProcAddress(hDll, "SetConfigName"); GetCounterFunc getCounterFunc = (GetCounterFunc)GetProcAddress(hDll, "GetInternalCounter"); SetCounterFunc setCounterFunc = (SetCounterFunc)GetProcAddress(hDll, "SetInternalCounter"); if (createFunc && destroyFunc && execFunc && getIdFunc && getConfigFunc && setConfigFunc && getCounterFunc && setCounterFunc) { void* pInstance = createFunc(); // 调用业务函数 execFunc(pInstance); // 获取只读属性 int id = getIdFunc(pInstance); std::cout << "只读属性ReadOnlyId的值:" << id << std::endl; // 读写属性操作 char* configName = getConfigFunc(pInstance); std::cout << "初始ConfigName:" << configName << std::endl; LocalFree(configName); // 释放C#分配的内存 setConfigFunc(pInstance, "UpdatedByCpp"); configName = getConfigFunc(pInstance); std::cout << "修改后的ConfigName:" << configName << std::endl; LocalFree(configName); // 受保护写入属性操作 int counter = getCounterFunc(pInstance); std::cout << "初始InternalCounter:" << counter << std::endl; setCounterFunc(pInstance, 10); counter = getCounterFunc(pInstance); std::cout << "修改后的InternalCounter:" << counter << std::endl; // 释放实例 destroyFunc(pInstance); } FreeLibrary(hDll); return 0; }
关键注意事项
- 权限处理:对于受保护写入的属性,C++无法直接访问C#的protected成员,必须通过C#类内部的公开方法间接设置,你可以在这个公开方法里添加权限校验逻辑。
- 类型转换:C#的
string在COM方式下对应C的_bstr_t,在P/Invoke方式下需要注意内存分配与释放(比如用Marshal.StringToHGlobalAnsi分配,C用LocalFree释放)。 - 可见性:所有需要暴露给C++的C#成员必须是
public的,COM方式下还要标记[ComVisible(true)]。
内容的提问来源于stack exchange,提问作者user4019882
相关产品推荐
相关产品推荐

