从C ++调用C ++ DLL可以正常工作,但从C#无法正常工作

8

我有一个名为tccdvc.dll的DLL,它是可在此处获得的SDK的一部分:

http://www.commell.com.tw/Download/Driver/Industrial%20Peripheral/Driver/MPX-885/MPX-885%20SDK%20(1.2)/SetupCOMMELL%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程序),以访问这个tccdvc.dll。这工作正常,无论是使用提供的.lib文件还是使用LoadLibrary / GetProcAddress。我还编写了一个C++ DLL(称其为mywrapper.dll),它使用tccdvc.dll,在两个版本中运行良好,一个版本使用.lib文件,另一个版本使用LoadLibrary / GetProcAddress。同样,这也很好用。此mywrapper.dll使用__cdecl调用约定。它包含一个名为InitMyWrapperDLL()的函数,该函数负责加载tccdvc.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(“tccdvc.dll”)调用返回NULL。在C#中,我正在使用:

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

...

InitMyWrapperDLL();

当使用提供的tccdvc.lib文件编译mywrapper.dll时,它也会失败,并显示错误代码0x8007045a(也称为1114),表示DLL初始化失败,并将mywrapper.dll作为DLL名称。结果发现,故障是由于通过mywrapper.dll加载的tccdvc.dll引起的。

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

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

...

TCC_DVCOpen();

我还在声明中使用了“unsafe”,但是这没有任何区别。这是可以预料的,因为LoadLibrary()失败,所以甚至无法到达TCC_DVCOpen()。
为了确定问题,我再次使用了LoadLibrary/GetProcAddress版本的mywrapper.dll,并将以下代码放入我的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();

在此之后,hdll1具有有效值,但hdll2为0。使用.NET 3.5框架时,GetLastError()再次返回0x8007045a,但是在使用.NET 4.0时,GetLastError()返回0(ERROR_SUCCESS)。
我使用Sysinternals的Process Monitor获取更多信息,可以看到tccdvc.dll被成功读取和映射。 Process Monitor显示的任何内容都没有给出原因,说明为什么在使用C#时失败,而在使用C ++时不会失败。
有什么想法吗? 谢谢!

使用Dependency Walker工具从[链接](http://dependencywalker.com)显示了5个直接依赖项:kernel32.dll,user32.dll,advapi32.dll,ole32.dll,oleaut32.dll,这些都存在于系统中。深入到advapi32.dll中的许多级别中,它显示延迟加载依赖项ieshims.dll和wer.dll不存在。但是,根据Process Monitor的记录,它从未尝试加载这些依赖项。 - zarulad
在加载过程中,tccdvc.dll 是否会产生任何额外的线程? - qehgt
如果你没有那个 DLL 的源代码,那么你需要联系供应商寻求支持。 - Hans Passant
@qehgt:我不确定。很遗憾,我也不知道如何找出答案。 - zarulad
1
P/Invoking调用GetLastError并不可靠,需要使用Marshal.GetLastWin32Error。 - Logan Capaldo
显示剩余6条评论
2个回答

1

我有几个建议:

  • 您可以创建一个C++/CLI类库项目,然后在您的C#项目中引用它。
  • 在我的情况下,我发现UnmanagedFunctionPointerAttribute对于调用是必不可少的。
  • 有一种情况是,无论我做什么,从C#调用.DLL都不起作用,只有.LIB对我有用(这意味着实施我的第一个建议)。故障排除表明,“DLL空间”不适合该特定库。

(关于最后一句话:我绝不是C++专家,实际上那是我迄今为止唯一做过的项目。这当然值得更多的细节,但我从未知道也没有找到问题的根源,但因为我只是需要它,所以它被解决了。感谢指出任何错误/更好的解释。)

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

如何从C#使用C回调? 这是.DLL的链接,如果您想查看所有代码,请询问。

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

愿原力与你同在 :-)


0

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接