从C++调用C++DLL是可行的,但从C#调用则不然

Calling C++ DLL from C++ works, but not from C#

本文关键字:调用 但从 C++DLL C++      更新时间:2023-10-16

我有一个名为tcdvc.DLL的DLL,它是这里可用的SDK的一部分:

http://www.commell.com.tw/Download/Driver/Industrial%20Peripheral/Driver/MPX-885/MPX-885%20SDK%20(1.2(/SetupCOMELL%20MPX-885_20100627.rar

DLL是用C++编写的,检查DLL显示它是用链接器6.0版本链接的,所以我认为它是用VC++6.0编写的。DLL没有附带源代码,只有一个.lib文件和一个.h文件。所有导出的函数都声明为extern"C"(因此没有C++名称篡改(和APIENTRY(因此__stdcall(。

我在Windows XP SP3(32位(上的Visual Studio 2010中编写了一个C++(而不是.NET(程序来访问此tcdvc.dll。无论是在使用提供的.lib文件还是在使用LoadLibrary/GetProcAddress时,这都能很好地工作。我还编写了一个C++DLL(让我们称之为mywrapper.DLL(,它使用tcdvc.DLL,并且有两个版本,一个使用.lib文件,另一个使用LoadLibrary/GetProcAddress。同样,这很好用。此mywrapper.dll使用__cdecl调用约定。它包含一个名为InitMyWrapperDLL((的函数,用于加载tcdvc.dll。使用LoadLibrary/GetProcAddress的mywrapper.dll版本的代码如下:

typedef int (APIENTRY *TCCPROCTYPE01)();
HMODULE TCCmodule;
TCCPROCTYPE01 Proc_TCC_DVCOpen;
extern "C" __declspec(dllexport) void InitMyWrapperDLL ()
{ TCCmodule = LoadLibrary("tccdvc.dll");
  Proc_TCC_DVCOpen = (TCCPROCTYPE01)GetProcAddress(TCCmodule, "TCC_DVCOpen");
  ...
}

同样,使用C++前端,这可以很好地工作。但是,当从C#(在同一台机器上(调用它时,LoadLibrary("tcdvc.dll"(调用返回NULL。在C#中,我使用的是:

[DllImport("mywrapper.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi, ExactSpelling=true, EntryPoint="InitMyWrapperDLL")]
private static extern void InitMyWrapperDLL ();
...
InitMyWrapperDLL();

当使用提供的tcdvc.lib文件编译mywrapper.dll时,它也会失败,错误代码为0x8007045a(也称为1114(,这意味着dll初始化失败,并且它将mywrapper.dll作为dll的名称。事实证明,失败是因为tcdvc.dll,它是通过mywrapper.dll加载的。

在C#中使用以下内容也会失败:

[DllImport("tcc.dll", CallingConvention=CallingConvention.StdCall, CharSet=CharSet.Ansi, ExactSpelling=true, EntryPoint="TCC_DVCOpen")]
private static extern Int32 TCC_DVCOpen ();
...
TCC_DVCOpen();

我在声明中也使用了"不安全",但这没有任何区别。可预测,因为LoadLibrary((失败,所以它甚至无法访问TCC_DVCOpen((。

为了找出问题,我再次使用了mywrapper.dll的LoadLibrary/GetProcAddress版本,并在我的C#程序中放入了以下代码:

[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary (string lpLibFileName);
[DllImport("kernel32.dll")]
private static extern Int32 GetLastError ();
...
IntPtr hdll1 = LoadLibrary("mywrapper.dll");
IntPtr hdll2 = LoadLibrary("tccdvc.dll");
Int32 errcode = GetLastError();

在此之后,hdl1具有一个有效值,但hdl2为0。使用.NET 3.5 Framework时,GetLastError((再次返回0x8007045a,但使用.NET 4.0时,GetLastError((返回0(ERROR_SUCCESS(。

我使用Sysinternals的Process Monitor来获取更多信息,我可以看到tccdvc.dll正在被成功读取和映射。Process Monitor显示的任何内容都没有给我任何提示,说明它在使用C#时失败的原因,但在使用C++时却没有。

有什么想法吗?谢谢

我有几个建议给你:

  • 您可以创建一个C++/CLI类库项目,然后在C#项目中引用它
  • 在我的案例中,我发现UnmanagedFunctionPointerAttribute对调用至关重要
  • 有一种情况是,无论我做什么,从C#调用.DLL都不起作用,只有.LIB对我起作用(这意味着实现我的第一个建议(。故障排除使我发现"DLL空间"不适合该特定库

(关于最后一句话:无论如何,我不是C++专家,实际上这是我迄今为止做的唯一一个项目。这当然需要更多的细节,但我从不知道也不知道问题的根源,但它得到了修复,因为我只是需要它。感谢您指出任何错误/更好的解释。(

以下是适用于我的问题的一些解决方案:

如何使用C#的C回调?这里有指向.DLL的链接,如果你想查看我的所有代码,只需询问即可。

此外,在这个领域对我有帮助的一些工具:

  • 用于查看DLL导出的PE资源管理器
  • PInvoke互操作助手有助于创建PInvoke声明

愿力量与你同在:-(

因此,当从C++应用程序调用时,您的C代码可以工作,但当从.NET二进制文件调用时,相同的代码会失败,在LoadLibrary中失败。。。

这只是一种预感,所以我不确定是否描述了您的情况,但LoadLibrary有一些复杂的标准来解析相对DLL名称的路径,这些标准可能会根据某些参数而变化。我根本就不认识他们。可能是.NET二进制文件的EXE清单中的某些内容阻止了它根据这些规则查找DLL。您可以尝试调整PATH环境变量或使用绝对路径加载DLL。(您可以使用GetModuleFileName等来查找您自己代码的绝对路径…(