You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:28:23