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

如何从C#调用含std::function参数的非托管C++ DLL函数?

C#调用带std::function参数的非托管C++ DLL函数问题

问题背景

非托管C++ DLL导出函数定义:

typedef std::function<void (
                    unsigned int msgType,
                    unsigned int size,
                    const char* const value
                    ) > SCallback;

__declspec(dllexport) int StartS( SCallback fnS );

用户尝试的C#调用代码:

[DllImport("SeqLib.dll", EntryPoint = "?StartS@SApi@@YAKV?$function@$$A6AXIIPBD@Z@std@@@Z", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern int StartS(DCallback cb);

public delegate void DCallback(int msgType, int size, IntPtr value);

public static void MessageReceived(int msgType, int size, IntPtr value)
{
    // 处理逻辑
}

DCallback callback = new DCallback(MessageReceived);
StartS(callback);

运行时抛出异常:

System.AccessViolationException: "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

原实现思路的错误

直接用C# delegate 映射C++ std::function 是完全错误的,核心原因:

  • std::function 是C++标准库中的类类型,内部包含函数指针、捕获状态等复杂结构,并非单纯的函数指针。
  • C#的delegate在互操作时会被封送为C风格函数指针(仅指向函数入口),和std::function的参数类型完全不匹配,传递后会导致DLL内部访问非法内存,触发异常。

正确的调用方案

方案1:修改C++ DLL,导出C风格函数指针接口

如果有权限修改原DLL,建议将导出函数改为接受C风格函数指针,因为C#只能直接和C风格函数指针交互。

修改后的C++导出代码:

// 定义C风格函数指针类型
typedef void (*SCallbackPtr)(unsigned int msgType, unsigned int size, const char* value);

// 导出接受函数指针的接口,内部可将指针包装为std::function使用
__declspec(dllexport) int StartS(SCallbackPtr fnS) {
    std::function<void(unsigned int, unsigned int, const char*)> callback(fnS);
    // 原逻辑中使用callback即可
    return 0;
}

对应的C#调用代码:

[DllImport("SeqLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int StartS(DCallback cb);

// 参数类型严格匹配:uint对应unsigned int,IntPtr对应const char*
public delegate void DCallback(uint msgType, uint size, IntPtr value);

public static void MessageReceived(uint msgType, uint size, IntPtr value)
{
    // 将IntPtr转换为字符串示例
    string content = Marshal.PtrToStringAnsi(value, (int)size);
    // 处理逻辑
}

// 必须保留delegate引用,防止被GC回收
private static DCallback _callback;

public static void Init()
{
    _callback = new DCallback(MessageReceived);
    StartS(_callback);
}

方案2:使用C++/CLI编写中间桥接层

如果无法修改原DLL,需要通过C++/CLI作为中间层,完成托管C# delegate到非托管std::function的转换。

  1. 创建C++/CLI类库项目,引用原非托管DLL;
  2. 编写桥接代码:
#include <functional>
#include "OriginalDllHeader.h" // 原DLL的头文件

using namespace System;
using namespace System::Runtime::InteropServices;

namespace SApiBridge {
    // 定义与C#兼容的托管委托
    public delegate void ManagedCallback(UInt32 msgType, UInt32 size, IntPtr value);

    public ref class ApiWrapper {
    private:
        // 保留托管委托引用,防止被GC回收
        static gcroot<ManagedCallback^> s_managedCallback;

        // 非托管回调函数,转发到托管委托
        static void NativeCallback(unsigned int msgType, unsigned int size, const char* value) {
            s_managedCallback->Invoke(msgType, size, IntPtr((void*)value));
        }
    public:
        static int StartS(ManagedCallback^ cb) {
            s_managedCallback = cb;
            // 将非托管回调函数包装为std::function,传递给原DLL
            std::function<void(unsigned int, unsigned int, const char*)> nativeCallback(&NativeCallback);
            return ::StartS(nativeCallback);
        }
    };
}
  1. C#项目引用该C++/CLI类库,直接调用桥接层:
using SApiBridge;

public static void MessageReceived(uint msgType, uint size, IntPtr value)
{
    string content = Marshal.PtrToStringAnsi(value, (int)size);
    // 处理逻辑
}

// 保留委托引用防止GC回收
private static ManagedCallback _callback;

public static void Init()
{
    _callback = new ManagedCallback(MessageReceived);
    ApiWrapper.StartS(_callback);
}

内容的提问来源于stack exchange,提问作者Mike

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:03:18