简体   繁体   English

Delphi 个类,共享 memory,不同的 DLL 加载地址

[英]Delphi classes, shared memory, and varying DLL loading addresses

I'm working with an old and complex system that shares memory between dozens (sometimes hundreds) of Win32 processes.我正在使用一个古老而复杂的系统,该系统在数十个(有时数百个)Win32 进程之间共享 memory。 The code is mostly very old Pascal that has been ported to Delphi a few years ago.代码主要是几年前移植到 Delphi 的非常古老的 Pascal。

(Almost) all of the code is in a single DLL, which all of the processes load. (几乎)所有代码都在一个 DLL 中,所有进程都加载它。 At the moment, we have forced a fixed loading address of that DLL. Image base is defined and ASLR is disabled in linker settings.目前,我们已强制使用 DLL 的固定加载地址。在 linker 设置中定义了图像库并禁用了 ASLR。 Each process checks the DLL loading addresses at startup and the entire system refuses to work if the DLL cannot be loaded at the exact same address in all of the processes.每个进程在启动时都会检查 DLL 加载地址,如果 DLL 不能在所有进程中加载到完全相同的地址,则整个系统将拒绝工作。 This is of course a problematic solution.这当然是一个有问题的解决方案。 Sometimes customers have all sorts of 3rd party gadgets which affect the address space and prevents our product from having the address it wants for the DLL.有时,客户拥有各种影响地址空间并阻止我们的产品获得 DLL 所需地址的第 3 方小工具。

The reason for the fixed DLL loading address is below.固定DLL加载地址的原因如下。 I'm wondering if there is a way to work around this problem.我想知道是否有办法解决这个问题。

I've been trying to introduce object-oriented programming.我一直在尝试介绍面向对象的编程。 The problem is, if I instantiate a Delphi class in the shared memory, that instance now seems to be dependent on the DLL loading address.问题是,如果我在共享 memory 中实例化一个 Delphi class,该实例现在似乎依赖于 DLL 加载地址。 For example, if another process tries to destroy that object, it will crash, unless the two processes happen to have the same DLL address.例如,如果另一个进程试图破坏那个 object,它就会崩溃,除非这两个进程恰好具有相同的 DLL 地址。 Delphi runtime seems to save function addresses in the object instance, assuming they will stay fixed for the lifetime of the object. Delphi 运行时似乎在 object 实例中保存了 function 个地址,假设它们在 object 的生命周期内保持不变。

One possible solution might be to copy the DLL contents to the shared memory, then do some sort of magic trickery on DLL_PROCESS_ATTACH to make the process run that copy of the code instead of the loaded DLL address.一种可能的解决方案可能是将 DLL 的内容复制到共享的 memory,然后对 DLL_PROCESS_ATTACH 进行某种魔术操作,使进程运行该代码副本,而不是加载的 DLL 地址。 The shared memory we have is always mapped at the same addresses.我们拥有的共享 memory 始终映射到相同的地址。 (Yes, this is also a problem sometimes but very rarely since the shared memory can be mapped at high (above 2 GB) addresses which are easily available.) (是的,这有时也是一个问题,但很少见,因为共享的 memory 可以映射到容易获得的高地址(2 GB 以上)。)

Or is there a way to tell Delphi compiler "please do not assume that the addresses of the functions related to this class are fixed"?或者有没有办法告诉 Delphi 编译器“请不要假设与这个 class 相关的函数的地址是固定的”? I'm using Delphi 11.1.我正在使用 Delphi 11.1。

I was able to figure out aa solution that seems to work well, so let me answer my own question.我能够找出一个似乎运作良好的解决方案,所以让我回答我自己的问题。

The issue is that in order for dynamic dispatch to work, the object instance must be 'tagged' with type information.问题在于,为了使动态分派起作用,object 实例必须使用类型信息进行“标记”。 In the case of Delphi in Win32, this tag is in the first 32 bits of the object instance, and it is a memory address into the DLL where the the code of the class in question is.在 Win32 中的 Delphi 的情况下,此标记位于 object 实例的前 32 位中,并且它是 memory 地址到 DLL 中,其中 class 的代码是。

If you shift this address to match the variable (process-specific) address of the DLL, the dynamically dispatched methods work fine.如果您移动此地址以匹配 DLL 的变量(特定于进程的)地址,则动态分派的方法可以正常工作。 In order to do this, you need to compare this address to the loading address of the DLL (or any other reference address inside the DLL) and save the offset, when creating the object.为此,您需要在创建 object 时将此地址与 DLL(或 DLL 中的任何其他引用地址)的加载地址进行比较并保存偏移量。

Then, before calling the object's methods in another process, "localize" the object by taking the actual address of the DLL, adding the offset, and writing this sum to the first 32 bits of the object.然后,在另一个进程中调用该对象的方法之前,通过获取 DLL 的实际地址、添加偏移量并将此和写入 object 的前 32 位来“本地化”object。

Now you can use the object in any process, as long as you localize it first:现在你可以在任何进程中使用 object,只要你先本地化它:

Obj.Localize;
Obj.Do_Something;

This can be neatly wrapped in a class. Offset is simply a private 32-bit UInt32.这可以整齐地包装在 class 中。 Offset量只是一个私有的 32 位 UInt32。

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM