我从事的项目需要 .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 *buffer)
并导入为
[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”,尽管它看起来很简单。
我的第二个问题是:CLR 在 P/Invoke 期间如何匹配导出的函数名称?
在这种情况下,我认为 CLR 非常聪明,以至于它实际上隐藏了一个错误 - LoadIPWData 函数应该可以通过其名称从其他非托管模块访问。也许我有点偏执,但我更喜欢了解 CLR 的实际工作原理。不幸的是,我在该主题上的所有谷歌搜索都没有结果。