【问题标题】:How to call C function in DLL from C#?如何从 C# 调用 DLL 中的 C 函数?
【发布时间】:2019-02-14 13:51:54
【问题描述】:

我需要通过在C#中导入DLL来调用C的以下函数,但它给出了以下错误。

我已导入 DLL 并成功执行了其他函数,但此函数抛出错误。

C 方法:

long __stdcall VBVMR_Input_GetDeviceDescA(long zindex, long * nType, char * szDeviceName, char * szHardwareId);

C# 代码:

[DllImport("VoicemeeterRemote.dll", EntryPoint = "VBVMR_Input_GetDeviceDescA", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
private static extern int VBVMR_Input_GetDeviceDescA(long zindex, ref long nType, [Out] byte[] szDeviceName, [Out] byte[] szHardwareId);

long nType = 0;
byte[] c = new byte[100];
byte[] b = new byte[100];
long i=0;

int rep = VBVMR_Input_GetDeviceDescA(i,ref nType, c, b);

执行VBVMR_Input_GetDeviceDescA 时抛出异常:

对 PInvoke 函数 'Voicemeter!Voicemeter.Program::VBVMR_Input_GetDeviceDescA' 的调用使堆栈不平衡。这可能是因为托管 PInvoke 签名与非托管目标签名不匹配。检查 PInvoke 签名的调用约定和参数是否与目标非托管签名匹配。

【问题讨论】:

  • 不应该是__cdecl,而不是__stdcall吗? docs.microsoft.com/en-us/cpp/cpp/cdecl?view=vs-2017
  • 是dll中的__stdcall。我已使用以下内容进行导入。 [DllImport("VoicemeeterRemote.dll", EntryPoint = "VBVMR_Input_GetDeviceDescA", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
  • 您的问题中有CallingConvention = CallingConvention.Cdecl。除此之外,C 的long 是C# 的int。最重要的是,您应该使用CharSet.Ansi and StringBuilder 作为char * szDeviceName, char * szHardwareId
  • 将其更改为 CallingConvention.StdCall 的响应相同
  • @UmairAsghar 解决其他两个问题。

标签: c# c dll pinvoke


【解决方案1】:

如果导出的函数在导出它的 DLL 中使用调用约定 __stdcall 声明,则需要在 C# 代码中使用 CallingConvention.StdCall 导入它,而不是 Cdecl

这种调用约定不匹配之所以会出现“不平衡堆栈”的错误是因为在调用cdecl函数后,调用代码预计会在函数调用后调整堆栈指针寄存器以删除参数从堆栈传递给函数。但是,由于您的函数实际上是一个 stdcall 函数,它自己在它使用的 ret imm16 指令中执行此操作,因此当调用代码“清理堆栈”时,您的堆栈也会下溢。

下一个问题是 C# 中的 long 和 MSVC 中的 long 大小不同,因此即使使用 stdcall 调用约定,被调用的函数也会弹出与其认为正确大小对应的字节数对于参数列表,但由于C#的long与MSVC的long的大小不同,所以在DLL函数末尾的ret imm16指令之后堆栈仍然不平衡。但是,C# 的 int 类型对应于 long

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多