使用来自 C++ dll 的 __declspec(dllexport) 签名声明在 C# 中调用的问题

Posted

技术标签:

【中文标题】使用来自 C++ dll 的 __declspec(dllexport) 签名声明在 C# 中调用的问题【英文标题】:Issue using __declspec(dllexport) signature declaration from C++ dll to call in C# 【发布时间】:2010-12-24 07:13:11 【问题描述】:

我正在尝试调用 C++ dll 中声明为 __declspec(dllexport) 的方法以在 C# 中使用,但我不知道如何从 C++ 返回字符串值以及如何使用 DllImport 声明签名在 C# 中。

C++ 代码“VNVAPI.dll”

  __declspec(dllexport) char * GetGpuName(int phyGPUid)
  
      CNvidia * pInstance = CNvidia::GetInstance();
      char  szName[512]=0;
      pInstance->GetGpuName(phyGPUid,szName,512);
      return szName;
  

C#方法签名:

[DllImport("VNVAPI.dll")]
   public static extern  char GetGpuName(int phyGPUid);

产生错误:

调用 PInvoke 函数 '核心!Core.Hardware.IO.NVAPI::GetGpuName' 使堆栈不平衡。这是 可能是因为托管的 PInvoke 签名与非托管的不匹配 目标签名。检查 调用约定和参数 PInvoke 签名匹配目标 非托管签名。

谢谢。

【问题讨论】:

C++ 代码无效,它返回一个指向栈上缓冲区的指针。很经典的bug。这个函数也不能从 C++ 程序中安全地调用,尽管它往往会意外工作。它可以绝对不被 pinvoked,pinvoker marshaller 将破坏缓冲区内容。 @Hans Passant:当你说 C++ 时,这是否意味着在 C 中做同样的事情有不同的语义? @Hans Passant:Drat,我再次感到困惑,我以为你在这里提到了static 场景。我的脑海中仍然有@David Heffernan 的答案。 C 和 C++ 之间为 static 分配内存的方式有什么区别吗? @leppie,同样的事情。使用static 不是解决办法,pinvoke marshaller 将尝试使用 CoTaskMemFree 释放字符串。这在 Vista 和 Win7 上大放异彩。真正的解决方法是将缓冲区 + 长度作为参数传递,StringBuilder 在托管端。 @Hans Passant:我确实理解正确和理智的方式。但是为什么编组器会尝试释放它没有分配的内存呢?当然,它应该只是尝试复制指向的缓冲区(或使字符串指向内存位置,如果可能的话)。 【参考方案1】:

正如其他人所指出的,您需要在 P/Invoke 中指定 C 调用约定,并在托管端使用字符串来编组以空值结尾的 char*。

但是,您应该重新调整 C++ 例程以将 char* 作为输入参数以及缓冲区长度参数。然后,您在本机代码中写入此缓冲区。这避免了当前的问题,即您目前拥有代码的数据是从堆栈返回的,当然,堆栈会在函数返回时展开。

使用静态的建议将使这个内存全局化,从而避免堆栈展开问题,但会牺牲线程安全。是的,它可能适用于这个用例,但这是一个坏习惯。

【讨论】:

我会改变c++方法,它有你说的逻辑【参考方案2】:

错误消息可能令人困惑。

不赘述,试试这个:

static char  szName[512]=0;

如果仍然出现错误,则需要在DllImport 属性中指定调用约定。

编辑:

还为 C# 方法声明设置返回类型 string

【讨论】:

我试过了,但不起作用我仍然遇到同样的错误,还有其他方法可以返回字符串吗?谢谢。 你都试过了吗?返回类型必须是字符串,这一点很重要。 这可能会也可能不会解决问题。但这是不安全的,因为对 GetGpuName() 的并发调用会写入同一个缓冲区! @rstevens:我确实意识到了这一点,但堆栈变得不平衡仍然很有趣。【参考方案3】:

错误消息建议检查调用约定。我怀疑该函数使用的是 C 调用约定,这可以解释不平衡的堆栈。我建议您在 DllImport 属性中指定调用约定(如 @leppi 建议的那样)。像这样:

[DllImport("VNVAPI.dll", CallingConvention=CallingConvention.Cdecl)]

根据Strings samples,返回值应该是string

static extern string GetGpuName(int phyGPUid);

【讨论】:

仍然存在代码从堆栈返回数据的问题。

以上是关于使用来自 C++ dll 的 __declspec(dllexport) 签名声明在 C# 中调用的问题的主要内容,如果未能解决你的问题,请参考以下文章

C++ DLL导出的两种方式和链接的两种方式

如何使托管代码中的 c++ dll 中的其他 dll 可以使用方法?

C++ 生成 dll 和调用 dll 的方法实例(转)

c++ 如何生成dll文件?

VC DLL 中 C++ 符号的 MinGw 未定义符号

如何在 DLL 之间传递对象指针?