如何通过禁用名称篡改来PInvoke实例方法

How to PInvoke an Instance Method by disabling Name Mangling

本文关键字:PInvoke 实例方法 何通过      更新时间:2023-10-16

给定foo.dll 中的以下c++类

class a{
  private:
    int _answer;
  public:
    a(int answer) { _answer = answer; }
    __declspec(dllexport) int GetAnswer() { return _answer; }
}

我想要C#的pInvoke GetAnswer。为此,我使用以下方法:

[DllImport("foo.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint= "something")]
public static extern int GetAnswer(IntPtr thisA);

我传入一个指向a的IntPtr(我从其他地方得到的,这并不重要)。CallingConvention = CallingConvention.ThisCall确保正确处理

这个问题最酷的是,我知道到目前为止我是对的,因为它已经很好了使用Depends.exe,我可以看到"GetAnswer"导出为?GetAnswer@a@@UAEHXZ(或者类似的东西——重点是它的名字被篡改了)。当我把拼写错误的名字插入EntryPoint的"something"时,一切都很好!我花了大约一天的时间才开始使用Depends.exe,所以我将把它留在这里,作为对任何有类似问题的人的帮助。

我真正的问题是:有没有什么方法可以在GetAnswer上禁用C++名称篡改,这样我就不需要把被篡改的名称作为我的入口点。在那里有一个被篡改的名称似乎可能会崩溃,因为我对名称篡改的理解是,如果编译器改变,它可能会改变。此外,对我想pInvoke的每个实例方法使用Depends.exe也是一件令人头疼的事。

编辑:忘记添加我尝试过的内容:我似乎无法将外部"C"放在函数声明上,尽管我可以将其放在定义上。这似乎没有帮助(当你仔细想想的时候,这是显而易见的)

我能想到的唯一其他解决方案是一个c风格的函数,它封装实例方法并将a的实例作为参数。然后,禁用该包装器上的名称篡改,并取消它。不过,我宁愿坚持我现有的解决方案。我已经告诉我的同事pInvoke很棒。如果我不得不在我们的c++库中放入特殊函数来使pInvoke工作,我会看起来像个白痴。

您不能禁用C++类方法的mangling,但您可以使用/EXPORT或.def文件以您选择的名称导出函数。

然而,您的整个方法是脆弱的,因为您依赖于实现细节,即this作为隐式参数传递。更重要的是,导出类的单个方法会带来痛苦。

将C++类暴露在.net语言中最明智的策略是:

  1. 创建平面C包装器函数并p/调用这些函数
  2. 创建一个C++/CLI混合模式层,该层发布封装本机类的托管类

在我看来,备选方案2更可取。

您可以使用注释/链接器#pragma将/EXPORT开关传递给链接器,链接器应允许您重命名导出的符号:

#pragma comment(linker, "/EXPORT:GetAnswer=?GetAnswer@a@@UAEHXZ")

不幸的是,这并不能解决您需要使用dependent或其他工具查找损坏的名称的问题。

您不必禁用被破坏的名称,它实际上包含了函数本身如何声明的大量信息,它基本上代表了函数名称被破坏后的整个签名。我知道你已经找到了一个单词,而另一个答案已经被标记为正确答案。我在下面写的是我们如何让它按你的意愿工作。

[DllImport("foo.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint = "#OrdinalNumber")]
public static extern int GetAnswer(IntPtr thisA);

如果将"#OrdinalNumber"替换为GetAnsweer的实数,例如"#1",它将按您的要求工作。

您可能只认为EntryPoint属性与我们传递给GetProcAddress的函数名称相同,在GetProcAddress中您可以传递函数名称或函数的序号。

您调用C++类的非静态函数成员的方法确实是正确的,并且正确使用了thiscall,这正是thiscall调用约定在C#p/Invoke中发挥作用的原因。这种方法的问题是,你必须查看DLL的PE信息,导出函数信息,并找出你想调用的每个函数的序号,如果你有大量的C++函数要调用,你可能想自动化这样的过程。

来自问题作者:我实际使用的解决方案

我最终使用了一个c样式函数,该函数封装了instance方法,并将a的一个实例作为参数。这样,如果类确实从继承,那么将调用正确的虚拟方法。

我故意选择不使用C++/CLI,因为它只是需要管理的又一个项目。如果我需要在一个类上使用所有的方法,我会考虑它,但我实际上只需要这一个序列化类数据的方法。