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

C#调用带__stdcall的非托管C++ DLL函数遇入口点错误求助

解决C++ DLL跨C#调用的入口点与类型兼容问题

先解决入口点找不到的问题

你遇到的入口点错误,核心原因是x64平台下MSVC不区分__stdcall/__cdecl等调用约定——所有函数都用统一的x64调用约定,你写的__stdcall会被编译器直接忽略。这导致C#里指定CallingConvention.StdCall完全无效,反而会干扰入口点匹配。

修正步骤:

  1. 用VS开发者命令提示符重新确认DLL的导出名称,执行:
    dumpbin /exports mydll.dll
    
    复制输出里对应Function的完整修饰名(就是带?的那串),确保C#的EntryPoint完全匹配,符号、大小写一个都不能错。
  2. 修改C#的DllImport,把调用约定改成CallingConvention.Winapi(x64下Winapi等价于默认的x64调用约定),或者直接省略调用约定参数(x64下默认就是正确的):
    [DllImport("mydll.dll", EntryPoint = "?Function@NS@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@H@Z", CallingConvention = CallingConvention.Winapi)]
    static extern IntPtr Function(int Num); // 这里先把返回值改成IntPtr,因为std::string不能直接传
    

关键问题:std::string不能直接跨语言传递

就算入口点找到了,直接返回std::string给C#一定会崩溃——std::string是C++编译器实现的私有类,内存布局和C#的String完全不兼容,跨语言传递属于未定义行为。必须改成C兼容的类型,推荐两种靠谱方案:

方案1:返回C风格字符串(推荐简单场景)

修改C++函数,返回用CoTaskMemAlloc分配的堆内存字符串(C#可以直接释放):

namespace NS
{
    __declspec(dllexport) char* __stdcall Function(int Num)
    {
        std::string result = "计算结果:" + std::to_string(Num);
        // 分配足够存字符串加结束符的内存
        char* buffer = (char*)CoTaskMemAlloc(result.length() + 1);
        if (buffer != nullptr)
        {
            strcpy_s(buffer, result.length() + 1, result.c_str());
        }
        return buffer;
    }
}

C#里调用并释放内存:

[DllImport("mydll.dll", EntryPoint = "?Function@NS@@YAPEADH@Z", CallingConvention = CallingConvention.Winapi)]
static extern IntPtr Function(int Num);

// 使用示例
IntPtr strPtr = Function(456);
string result = Marshal.PtrToStringAnsi(strPtr);
Marshal.FreeCoTaskMem(strPtr); // 必须释放,避免内存泄漏

方案2:用回调函数传递数据(适合复杂类型如std::vector)

如果需要传递std::vector这类复杂容器,用回调让C主动把数据推给C#是更安全的方式:
C
代码:

// 先定义回调函数类型
typedef void(__stdcall* StringCallback)(const char* str);
typedef void(__stdcall* IntCallback)(int value);

namespace NS
{
    __declspec(dllexport) void __stdcall ProcessData(int inputNum, StringCallback strCb, IntCallback intCb)
    {
        // 返回字符串结果
        std::string strResult = "输入值:" + std::to_string(inputNum);
        strCb(strResult.c_str());
        
        // 传递std::vector里的元素
        std::vector<int> vec = {10, 20, 30};
        for (int num : vec)
        {
            intCb(num);
        }
    }
}

C#代码:

// 定义匹配的委托
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void StringCallback(IntPtr strPtr);

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate void IntCallback(int value);

[DllImport("mydll.dll", EntryPoint = "?ProcessData@NS@@YAXHP6GXPEBD@ZP6GXH@Z@Z", CallingConvention = CallingConvention.Winapi)]
static extern void ProcessData(int inputNum, StringCallback strCb, IntCallback intCb);

// 使用示例
StringCallback strCallback = (ptr) => {
    string str = Marshal.PtrToStringAnsi(ptr);
    Console.WriteLine(str);
};

IntCallback intCallback = (num) => {
    Console.WriteLine($"Vector元素:{num}");
};

ProcessData(789, strCallback, intCallback);

最后检查事项

  • 确保C++ DLL和C#程序都是x64架构,Debug/Release模式要一致;
  • 把DLL复制到C#程序的输出目录(比如bin/x64/Debug),避免找不到DLL;
  • 绝对不要直接传递std::string、std::vector这类C++专属类型,必须转成C兼容的类型或用回调传递。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 12:32:04