使用来自 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# 中调用的问题的主要内容,如果未能解决你的问题,请参考以下文章