在P / Invoke期间CLR如何匹配导出的名称?

时间:2012-05-06 18:59:30

标签: clr pinvoke

我从事的项目需要.Net与非托管代码的互操作性。几周前我开始使用.Net,虽然我有很多C / C ++的经验,但我很惊讶CLR如何处理P / Invoke。这是详细信息。我的同事写了这个函数

__declspec(dllexport) int __stdcall ReadIPWSensor(unsigned int deviceClassId, void *buffer) {...}

我必须从C#模块调用它。我导入了函数

[DllImport("ipw", CallingConvention = CallingConvention.StdCall)]
extern static int ReadIPWSensor(uint deviceClassId, IntPtr buffer);

只是为了找出异常(System.EntryPointNotFoundException,无法在DLL'ipw'中找到名为'ReadIPWSensor'的入口点)。我使用了DependencyWalker工具,发现函数导出为?ReadIPWSensor @@ YGHIPAX @ Z(我的同事忘了将它导出到DEF文件中)。只是为了快速测试(非托管DLL编译速度非常慢)我将导入定义更改为:

[DllImport("ipw", EntryPoint = "#22", CallingConvention = CallingConvention.StdCall)]
extern static int ReadIPWSensor(uint deviceClassId, IntPtr buffer);

因为序数为22.测试成功通过了新的导入定义。

我的第一个问题是:处理损坏的函数导出时有哪些好的做法?使用出口序数是一种好习惯吗?

在我的情况下,我可以访问C ++源代码和DEF文件,因此我添加了导出并将导入定义更改为

[DllImport("ipw", CallingConvention = CallingConvention.StdCall)]
extern static int ReadIPWSensor(uint deviceClassId, IntPtr buffer);

我知道我们已经在项目中使用了另一个功能,并希望将我的代码与现有代码进行比较。该函数定义为

  

extern“C”__ declspec(dllexport)int __stdcall LoadIPWData(void   *缓冲液)

并导入为

[DllImport("ipw", CallingConvention = CallingConvention.StdCall)]
extern static int LoadIPWData(IntPtr buffer);

令我惊讶的是DependencyWalker工具显示该函数被导出为_LoadIPWData @ 4(我的同事忘了再次将其导出到DEF文件中)。但是,使用此函数时,没有System.EntryPointNotFoundException错误。显然,CLR设法解决了正确的名称。似乎存在某种允许CLR找到正确功能的回退机制。我可以很容易地想象它总结了参数的大小,并且正在寻找“function_name @the_sum_of_all_parameter_sizes”,虽然看起来很简单。

我的第二个问题是:在P / Invoke期间CLR如何与导出的函数名匹配?

在这种情况下,我认为CLR非常聪明,它实际上隐藏了一个错误 - LoadIPWData函数应该可以通过其名称从其他非托管模块访问。也许我有点偏执但我更喜欢知道CLR究竟是如何工作的。不幸的是,我对该主题的所有谷歌搜索都毫无结果。

1 个答案:

答案 0 :(得分:2)

pinvoke marshaller具有一些常见的DLL导出命名方案的内置知识。它知道__cdecl函数通常具有前导下划线,并且32位模式下的__stdcall通常用前导下划线和尾随@x来装饰,其中x是在堆栈上传递的参数的大小(以字节为单位)。它还知道winapi函数是使用尾随的额外A或W导出的,这是一种命名方案,用于区分接受字符串的函数以及同时具有ansi和Unicode版本的函数。相应的[DllImport]属性是CharSet。它只是尝试它们直到找到匹配。

它对C ++编译器名称修饰规则(又称修改)一无所知,所以你必须使用extern "C"来手动压缩它。