在C#中调用C语言dll

7

问题介绍:

我需要通过提供DLL文件、LIB文件和C头文件的API来控制某个设备,这些函数被声明为dllimport

在使用C++项目中的API时一切都很顺利——我包含了头文件、LIB、DLL,并按照头文件中的声明调用了函数。

但是,当尝试从C#.NET项目中使用[DllImport]属性调用这些函数时,问题就开始了:函数被声明为具有精确名称和参数,运行代码时没有抛出任何异常。然而,设备完全没有响应,就好像这些函数实际上从未被调用过一样。

以下是在C头文件中的声明方式:

int __declspec(dllimport) Init_Card_RTx(unsigned short device_num, unsigned short enabled_channels, unsigned short xmt_channels);

C# 中如何声明:

[DllImport(Path, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Init_Card_RTx")]
public static extern int Init_Card_RTx(UInt16 device_num, UInt16 enabled_channels, UInt16 xmt_channels);

问题:

  • 这是因为头文件中的函数被声明为dllimport吗?
  • 如果是这样,我是否需要用C++函数包装DLL并声明为dllexport?
  • C DLL可从C#访问需要哪些步骤?
  • 在C#项目中,我需要同时包含LIB文件和DLL文件吗?而不仅仅是DLL文件本身?

能否提供您正在使用的导出和导入签名,这将会很有帮助。如果没有它们,给出明确的答案是困难的。 - J...
不,不是,没有,不行。很明显你对这个问题的猜测是不起作用的。没有人能看到你犯的错误,这不是YouTube。 - Hans Passant
我看到有几个人投票关闭这个问题,请不要这样做。C#/C++ P/invoke编程问题肯定是Stack Overflow的主题,对于可能没有深入了解P/invoke等内容的人来说,这似乎是一个相当好写的问题。 - Orion Edwards
4个回答

4
可能是因为头文件中的函数被声明为dllimport。这些函数需要被导出。它们是否被导出取决于将DLL编译成什么样子,由谁提供给你。我猜它们应该被导出(否则C++代码会尝试导入它们并失败)。解决这种问题时,我首先要做的是在Dependency Walker中加载DLL,它会显示所有导出的函数。如果它们没有显示出来,那么它们就没有被导出,C#无法调用它们。如果它们显示出来了,那么你就没问题了,不需要更改任何C代码或创建任何包装函数。
不需要。C#只能使用DllImport调用C函数。当导出函数时,C++进行name mangling,这使得事情变得混乱,通常是不可行的。
你需要做的是以某种方式导出函数。我的猜测是它们已经被导出了,但如果没有,你可以像这样创建一些包装函数。
int __declspec(dllexport) Init_Card_RTx(unsigned short device_num, unsigned short enabled_channels, unsigned short xmt_channels) {
    // just call the other function here
}

这些应该使函数被导出,你应该能在依赖项查看器中看到它。
需要哪些步骤才能从C#访问C DLL?
CLR必须能够找到dll(通常只需将其放在与C# exe相同的目录中),并且函数必须作为C函数导出。就是这样。
在C#项目中,我需要包含LIB文件吗?不仅仅是DLL文件本身?
你根本不需要在C#项目中包含任何内容,只需要使用带有DllImport属性的公共静态外部包装函数即可。只要CLR在运行时可以找到dll,那就是你所需要的。如果在调用第一个导入方法时找不到它,则应该会收到异常。
PS:获取依赖项查看器。我无法更高度推荐它了。

0
函数被声明为 dllimport 的原因是(如果我说错了,请纠正我)DLL 是由您的卡供应商提供的,这没问题,因为只有在构建 DLL 时才需要 dllexport

[DllImport(Path, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Init_Card_RTx")]

第一个参数Path只是为了说明而已,我说得对吗?

你能用depends.exe或PE Explorer(http://www.heaventools.com/download-pe-explorer.htm)打开DLL并查看其导出表吗?

我问这个是因为函数名可能会被修饰,如果DLL是以C++编译而不是纯C,则可能会发生这种情况。


0

尝试像这样声明它(只需在 CPP 文件中执行一次,而不是头文件中)

 extern "C" __declspec(dllexport) int __stdcall Init_Card_RTx(unsigned short device_num, unsigned short enabled_channels, unsigned short xmt_channels)
 {
    //
 }

而在C#中:

 [DllImport("MyDll.dll")]
 int Init_Card_RTx(ushort device_num, ushort enabled_channels, ushort xmt_channels);

你需要将其导出并使用 extern "C" 进行声明。__stdcall 是方便的,因为它是 .net 端的默认设置。我在我的 C++ 项目中有一个宏,以确保每次都输入相同的内容。

还有,记得使用 dependencywalker 工具来检查依赖项...


0
这种事情总是让人头疼。你必须确保传递的数据的字节结构与你的 DLL 期望的完全匹配。在 C# 中,本地结构并不知道它们正在被外部传递,因此你必须更加严格地组织代码。
更多信息请见此处:

http://msdn.microsoft.com/en-us/library/awbckfbz(v=vs.110).aspx


目前我甚至不涉及结构体,只是调用一些带参数的函数。 - Giora Ron Genender
这是一个很有道理的观点 - 你没有使用复杂结构。所有传递的变量实际上都是结构体。你需要在两边进行匹配;例如,如果你传递了一个Int32,而你的DLL期望一个UINT32,那么事情就不会总是正常工作。字符串是结构体,需要特殊处理。有一个整个网站专门展示了如何为Windows API做到这一点,也许你可以从中获得一些好的例子。http://www.pinvoke.net/ - Xavier J

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