Delphi类、共享内存和不同的DLL加载地址

js4nwp54  于 2022-11-29  发布在  其他
关注(0)|答案(1)|浏览(233)

我正在使用一个老而复杂的系统,它在几十个(有时是几百个)Win32进程之间共享内存。代码大多是非常老的Pascal,几年前被移植到 Delphi 。
(几乎)所有的代码都在一个DLL中,所有的进程都加载它。目前,我们强制该DLL的一个固定加载地址。在链接器设置中定义了图像库并禁用了ASLR。每个进程在启动时检查DLL加载地址,如果在所有进程中DLL不能加载到完全相同的地址,则整个系统拒绝工作。这当然是一个有问题的解决方案。有时客户有各种各样的第三方小工具,影响地址空间,并阻止我们的产品拥有它想要的DLL地址。
固定DLL加载地址的原因如下。我想知道是否有一种方法可以解决这个问题。
我一直在尝试介绍面向对象的编程。问题是,如果我在共享内存中示例化一个 Delphi 类,这个示例现在似乎依赖于DLL加载地址。例如,如果另一个进程试图破坏那个对象,它就会崩溃,除非这两个进程碰巧有相同的DLL地址。Delphi运行时似乎在对象示例中保存函数地址。假设它们在对象的生存期内保持固定。
一种可能的解决方案是将DLL内容复制到共享内存中,然后对DLL_PROCESS_ATTACH进行某种魔术般的欺骗,使进程运行该代码副本,而不是加载的DLL地址。我们拥有的共享内存总是Map到相同的地址。(是的,这有时也是一个问题,但很少,因为共享内存可以Map到很容易获得的高(2 GB以上)地址。)
或者有没有办法告诉 Delphi 编译器“请不要假设与这个类相关的函数的地址是固定的”?我使用的是Delphi 11.1。

tsm1rwdh

tsm1rwdh1#

我能够想出一个似乎效果很好的解决方案,所以让我回答我自己的问题。
问题是为了使动态调度工作,对象示例必须用类型信息“标记”。在Win32的 Delphi 中,该标记位于对象示例的前32位,并且它是DLL中的内存地址,其中有问题的类的代码位于其中。
如果您移动这个地址以匹配DLL的变量(特定于进程的)地址,动态调度的方法将正常工作。为此,您需要将这个地址与DLL的加载地址(或DLL中的任何其他引用地址)进行比较,并在创建对象时保存偏移量。
然后,在另一个行程序中呼叫对象的方法之前,先取得DLL的实际位址,加上位移,然后将总和写入对象的前32比特,以“当地语系化”对象。
现在您可以在任何流程中使用该对象,只要您先将其本地化即可:

Obj.Localize;
Obj.Do_Something;

这可以被巧妙地 Package 在一个类中。Offset只是一个私有的32位UInt 32。

constructor Global_Object.Create;

begin
   Self.Offset := PUInt32(Self)^ - Self.Reference_Address;
end;

procedure Global_Object.Localize;

begin
   PUInt32(Self)^ := Self.Reference_Address + Self.Offset;
end;

destructor Global_Object.Destroy;

begin
   inherited Destroy;
end;

function Global_Object.Reference_Address
                           : Cardinal;

begin
   // Anything in the DLL can be used as a reference,
   // such as the address of this function.

   Result := Cardinal(@Global_Object.Reference_Address);
end;

相关问题