从 Delphi DLL 导出全局符号

发布于 2024-09-17 08:25:13 字数 768 浏览 3 评论 0原文

我正在尝试在 Delphi 中创建一个与 Gecko 2.0 兼容的 DLL。

以前(Gecko 2.0 之前)DLL 需要导出 NSGetModule() 函数。这工作完美无缺。

从 Firefox 4 开始,我的 DLL 正在加载(我已经通过初始化部分中的断点验证了这一点),但我的 NSGetModule() 函数不再被调用。这是设计行为,因为从 Gecko 2.0 (Firefox 4) 开始,二进制组件不应导出 NSGetModule() 函数:

https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_2.0#Binary_components

根据这些文档,我的 DLL 需要导出一个 NSModule 数据符号,该符号指向到一个结构体。在 Delphi 术语中,我假设这是一个指向 Delphi 记录的全局变量。

在 C++ 中,这就是导出(全局)数据符号的方式:

define NSMODULE_DEFN(_name) extern "C" NS_EXPORT mozilla::Module const *const NSModule

我的问题:如何在 Delphi 中完成此操作?如何导出全局变量?

感谢您的反馈。

I'm trying to create a Gecko 2.0-compatible DLL in Delphi.

Previously (pre-Gecko 2.0) the DLL needed to export a NSGetModule() function. This worked flawlessly.

Starting with Firefox 4, my DLL is getting loaded (I have verified this though a breakpoint in my initialization section), but my NSGetModule() function does not get called anymore. This is the designed behavior because starting with Gecko 2.0 (Firefox 4), a binary component is not supposed to export a NSGetModule() function:

https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_2.0#Binary_components

According to these docs, my DLL needs to export a NSModule data symbol which points to a struct. In Delphi terminology, I assume this is a global variable which points to a Delphi record.

In C++, this is how you export the (global) data symbol:

define NSMODULE_DEFN(_name) extern "C" NS_EXPORT mozilla::Module const *const NSModule

My question: how do I accomplish this in Delphi? How do I export a global variable?

I appreciate your feedback.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

鹊巢 2024-09-24 08:25:13

Delphi 从 DLL 导出全局变量的方式与导出函数的方式类似:

library exp;
var
  global: Integer;
exports global;
end.

Delphi 可以从 DLL 导入全局变量,但这有点麻烦:声明一个与要导入的全局同名的 DLL 导入过程,然后获取程序的地址并进行适当调整。从Delphi 的角度来看,DLL 导入过程是通过DLL 导入表进行间接跳转的存根。导出的变量由操作系统加载器通过将导出的全局地址放入导入表中进行链接,几乎与导出过程的地址的修补方式完全相同。

例如:

{$apptype console}

procedure global; external 'exp.dll';

function GetGlobalAddr: PInteger;
type
  PPPointer = ^PPointer;
var
  p: PByte;
begin
  p := @global;
  Assert(p^ = $FF); // $FF $25 => indirect jump m32
  Inc(p);
  Assert(p^ = $25);
  Inc(p);
  Result := PPPointer(p)^^
end;

begin
  Writeln(GetGlobalAddr^);
end.

当然,后者的细节取决于实现和平台等。也许更安全的方法是将 LoadLibraryGetProcAddress 一起使用,这将在传递全局变量的名称时返回全局变量的地址。当然,这也依赖于平台。

64 位更新:

在 Windows 上的 64 位中,代码略有不同。操作码相同,但同一指令序列的寻址方式不同;它不是 32 位绝对偏移,而是 32 位相对偏移。

function GetGlobalAddr: PInteger;
type
  PPPointer = ^PPointer;
var
  p: PByte;
  ofs: Integer;
begin
  p := @global;
  Assert(p^ = $FF); // $FF $25 => indirect jump m32
  Inc(p);
  Assert(p^ = $25);
  Inc(p);
  // 32-bit offset follows
  ofs := PInteger(p)^;
  // offset is relative to next instruction
  Inc(p, SizeOf(ofs) + ofs);
  Result := PPPointer(p)^^
end;

Delphi exports global variables from DLLs in a similar way to how it exports functions:

library exp;
var
  global: Integer;
exports global;
end.

Delphi can import global variables from DLLs, but it's a bit of a hack: declare a DLL import procedure of the same name as the global to import, then get the address of the procedure and adjust it appropriately. DLL imported procedures, from Delphi's perspective, are stubs that do an indirect jump through the DLL import table. Exported variables are linked by the OS loader by putting the address of the exported global in the import table, almost exactly like how addresses of exported procedures are similarly patched in.

For example:

{$apptype console}

procedure global; external 'exp.dll';

function GetGlobalAddr: PInteger;
type
  PPPointer = ^PPointer;
var
  p: PByte;
begin
  p := @global;
  Assert(p^ = $FF); // $FF $25 => indirect jump m32
  Inc(p);
  Assert(p^ = $25);
  Inc(p);
  Result := PPPointer(p)^^
end;

begin
  Writeln(GetGlobalAddr^);
end.

Of course, the latter details are implementation and platform dependent etc. Probably a safer approach is to use LoadLibrary with GetProcAddress, which will return the address of the global variable when passed its name. Of course, that's also platform dependent.

64-bit update:

In 64-bit on Windows, the code is slightly different. The opcodes are the same, but the addressing mode for the same instruction sequence is different; instead of a 32-bit absolute offset, it's a 32-bit relative offset.

function GetGlobalAddr: PInteger;
type
  PPPointer = ^PPointer;
var
  p: PByte;
  ofs: Integer;
begin
  p := @global;
  Assert(p^ = $FF); // $FF $25 => indirect jump m32
  Inc(p);
  Assert(p^ = $25);
  Inc(p);
  // 32-bit offset follows
  ofs := PInteger(p)^;
  // offset is relative to next instruction
  Inc(p, SizeOf(ofs) + ofs);
  Result := PPPointer(p)^^
end;
拥醉 2024-09-24 08:25:13

阅读文档,我认为 Delphi 不允许直接导出全局变量,因为导出语句的帮助仅讨论例程。还有一个非常明确的

在共享中声明的全局变量
Delphi 无法导入库
应用程序。

并且可以安全地假设,如果 Delphi 无法导入它们,它也不会导出它们。

我想解决这个问题的方法可能是导出一个返回指向全局变量的指针的函数...

仅此几行:

type
  RGlobalRecord = record
    ...
  end;
  PGlobalRecord = ^RGlobalRecord;

var
  _GlobalRecord: RGlobalRecord;

function GetGlobalRecord: PGlobalRecord;
begin
  Result := @_GlobalRecord;
end;

exports GetGlobalRecord name 'ExternalNameOfGlobalRecord';

所以,如果 NSGetModule 函数返回与您现在需要导出为全局变量相同的结构,您可以尝试使用要导出的全局变量所需的名称来导出该函数:

exports NSGetModule name 'NSModule';

Reading the docs, I don't think Delphi allows for the export of global variables directly as the help on the exports statement discusses only routines. Also there is a very definite

Global variables declared in a shared
library cannot be imported by a Delphi
application.

and it is probably safe to assume that if Delphi can't import them, it also won't export them.

I suppose the way around this could be to export a function that returns a pointer to the global variable...

Something alone the lines of:

type
  RGlobalRecord = record
    ...
  end;
  PGlobalRecord = ^RGlobalRecord;

var
  _GlobalRecord: RGlobalRecord;

function GetGlobalRecord: PGlobalRecord;
begin
  Result := @_GlobalRecord;
end;

exports GetGlobalRecord name 'ExternalNameOfGlobalRecord';

So, if the NSGetModule function returns the same struct as you are now required to export as a global variable, you could try to export that function with a name as required for the global var to be exported:

exports NSGetModule name 'NSModule';
无法言说的痛 2024-09-24 08:25:13

这是我的德尔福解决方案。即使在 D5 中它也能工作:)

function MyComponentConstructor(aOuter: nsISupports; const IID: TGUID; out _result): nsresult; cdecl;
begin
  /* constructor */
end;


type
  TCIDEntry = record
    cid: ^TGUID;
    service: Boolean;
    getFactoryProc: Pointer;
    constructorProc: Pointer;
  end;

  TContractIDEntry = record
    constractid: PChar;
    cid: ^TGUID;
  end;

  TCategoryEntry = record
    category: PChar;
    entry: PChar;
    value: PChar;
  end;

  TModule = packed record
    mVersion: DWord;
    mCIDs: array of TCIDEntry;
    mContractIDs: array of TContractIDEntry;
    mCategoryEntries: array of TCategoryEntry;
    getFactoryProc: Pointer;
    loadProc: Pointer;
    unloadProc: Pointer;
  end;

  PModule = ^TModule;
  PPModule = ^PModule;

var
  mCIDs: array [0..1] of TCIDEntry =
  (
    ( cid: @Sample_cid; service: False; getFactoryProc: nil; constructorProc: @MyComponentConstructor ),
    ( cid: nil; service: False; getFactoryProc: nil; constructorProc: nil )
  );

  mContractIDs: array [0..1] of TContractIDEntry =
  (
    ( constractid: Sample_CONTRACTID; cid: @Sample_cid ),
    ( constractid: nil; cid: nil )
  );

  mCategoryEntries: array [0..2] of TCategoryEntry =
  (
    ( category: 'JavaScript-global-property'; entry: 'MyComponent'; value: Sample_CONTRACTID ),
    ( category: 'JavaScript-global-constructor'; entry: 'MyComponent'; value: Sample_CONTRACTID ),
    ( category: nil; entry: nil; value: nil )
  );

  NSModuleElem: TModule =
    (
       mVersion: 1;
       mCIDs: @mCIDs;
       mContractIDs: @mContractIDs;
       mCategoryEntries: @mCategoryEntries;
       getFactoryProc: nil;
       loadProc: nil;
       unloadProc: nil
    );

  NSModule: PModule = Addr(NSModuleElem);

exports
  NSModule name 'NSModule';

现在如果你可以向我发送 Delphi 中的 GenericClassInfo 实现,那就太棒了:)

Here is my Delphi solution. And it works even in D5 :)

function MyComponentConstructor(aOuter: nsISupports; const IID: TGUID; out _result): nsresult; cdecl;
begin
  /* constructor */
end;


type
  TCIDEntry = record
    cid: ^TGUID;
    service: Boolean;
    getFactoryProc: Pointer;
    constructorProc: Pointer;
  end;

  TContractIDEntry = record
    constractid: PChar;
    cid: ^TGUID;
  end;

  TCategoryEntry = record
    category: PChar;
    entry: PChar;
    value: PChar;
  end;

  TModule = packed record
    mVersion: DWord;
    mCIDs: array of TCIDEntry;
    mContractIDs: array of TContractIDEntry;
    mCategoryEntries: array of TCategoryEntry;
    getFactoryProc: Pointer;
    loadProc: Pointer;
    unloadProc: Pointer;
  end;

  PModule = ^TModule;
  PPModule = ^PModule;

var
  mCIDs: array [0..1] of TCIDEntry =
  (
    ( cid: @Sample_cid; service: False; getFactoryProc: nil; constructorProc: @MyComponentConstructor ),
    ( cid: nil; service: False; getFactoryProc: nil; constructorProc: nil )
  );

  mContractIDs: array [0..1] of TContractIDEntry =
  (
    ( constractid: Sample_CONTRACTID; cid: @Sample_cid ),
    ( constractid: nil; cid: nil )
  );

  mCategoryEntries: array [0..2] of TCategoryEntry =
  (
    ( category: 'JavaScript-global-property'; entry: 'MyComponent'; value: Sample_CONTRACTID ),
    ( category: 'JavaScript-global-constructor'; entry: 'MyComponent'; value: Sample_CONTRACTID ),
    ( category: nil; entry: nil; value: nil )
  );

  NSModuleElem: TModule =
    (
       mVersion: 1;
       mCIDs: @mCIDs;
       mContractIDs: @mContractIDs;
       mCategoryEntries: @mCategoryEntries;
       getFactoryProc: nil;
       loadProc: nil;
       unloadProc: nil
    );

  NSModule: PModule = Addr(NSModuleElem);

exports
  NSModule name 'NSModule';

Now if you can send me GenericClassInfo implementation in delphi that would be awesome :)

我的影子我的梦 2024-09-24 08:25:13

正如您所注意到的,这在 FF 5 和 FF 6 中不起作用。相反,您可以添加初始化块来在运行时检查 Firefox 版本,并相应地调整 mVersion。
Mozilla 有意破坏二进制组件,因此即使在不同版本之间,这也是一个可行的解决方法。

您可以使用 Application.ExeName 和 http://www.delphitricks.com/source- code/files/get_the_version_of_a_file.html

FF 5 - mVersion := 2;

FF 6 - m版本 := 6;

FF 7 - m版本 := 7;

As you have noticed this does not work in FF 5 and FF 6. Instead you can add initialization block to check for firefox version at runtime at adjust mVersion accordingly.
Mozilla is intentionally breaking binary components, so this is a workable workaround even between different versions.

You can use Application.ExeName and http://www.delphitricks.com/source-code/files/get_the_version_of_a_file.html

FF 5 - mVersion := 2;

FF 6 - mVersion := 6;

FF 7 - mVersion := 7;

放赐 2024-09-24 08:25:13

这是我当前的实现(这适用于 FF 5 和 FF 6 以及可能未来的所有其他版本)

type
  TCIDEntry = record
    CID: PGUID;
    Service: BOOL;
    GetFactoryProc: Pointer;
    ConstructorProc: Pointer;
  end;

  TContract = record
    ContractID: PChar;
    CID: PGUID;
  end;

  TCategory = record
    Category: PChar;
    Entry: PChar;
    Value: PChar;
  end;

  TModule = record
    Version: UINT;
    CIDs: Pointer;
    Contracts: Pointer;
    Categories: Pointer;
    GetFactory: Pointer;
    Load: Pointer;
    Unload: Pointer;
  end;

  PModule = ^TModule;

var
  NSModule: PModule;

implementation

var
  mtModule: TModule;
  CIDs: array[0..1] of TCIDEntry;
  Contracts: array[0..1] of TContract;

function GetFileVersionResourceInfo(const FileName, VerValue: string): string;
var
  S: string;
  Value: Pointer;
  ValueSize: DWORD;
  VerInfoSize: DWORD;
  VersionInfo: Pointer;
  GetInfoSizeJunk: DWORD;
begin
  // retrieve the size of the version information resource
  VerInfoSize := GetFileVersionInfoSize(PChar(FileName), GetInfoSizeJunk);
  if VerInfoSize > 0 then
  begin
    // retrieve memory to hold the version resource
    GetMem(VersionInfo, VerInfoSize);
    try
      // retrieve the version resource
      if GetFileVersionInfo(PChar(FileName), 0, VerInfoSize, VersionInfo) then
        if VerQueryValue(VersionInfo, '\\VarFileInfo\\Translation', Value, ValueSize) then
        begin
          S := '\\StringFileInfo\\' +
          IntToHex(LoWord(LongInt(Value^)), 4) +
          IntToHex(HiWord(LongInt(Value^)), 4) + '\\';
          if VerQueryValue(VersionInfo, PChar(S + VerValue), Value, ValueSize) then Result := PChar(Value);
        end;
    finally
      FreeMem(VersionInfo, VerInfoSize);
    end;
  end;
end;

function GetVersion: Integer;
var
  I: Integer;
  sProductVersion: string;
  sModuleFileName: array[0..MAX_PATH] of Char;
begin
  Result := 1; // Firefox 4
  FillChar(sModuleFileName, MAX_PATH, 0);
  if GetModuleFileName(0, sModuleFileName, SizeOf(sModuleFileName)) > 0 then
  begin
    sProductVersion := Trim(GetFileVersionResourceInfo(sModuleFileName, 'ProductVersion'));
    if (sProductVersion <> '') and (sProductVersion[1] in ['4'..'9']) then
    begin
      // Firefox 4 = version 1
      // Firefox 5 = version 2
      // Firefox 6 = version 6
      // etc.
      I := StrToInt(sProductVersion[1]);
      if I <= 5 then
        Result := I - 3
      else
        Result := I;
    end;
  end;
end;

function MyConstructor(aOuter: nsISupports; const aIID: TGUID; out aResult): nsresult; cdecl;
begin

end;

initialization
  mtModule.Version := GetVersion;

  CIDs[0].CID := @Sample_CID;
  CIDs[0].ConstructorProc := @MyConstructor;
  mtModule.CIDs := @CIDs;

  Contracts[0].ContractID := Sample_CONTRACTID;
  Contracts[0].CID := @Sample_CID;
  mtModule.Contracts := @Contracts;

  NSModule := @mtModule;

end.

Here's my current implementation (this works in FF 5 and FF 6 and probably all others going forward)

type
  TCIDEntry = record
    CID: PGUID;
    Service: BOOL;
    GetFactoryProc: Pointer;
    ConstructorProc: Pointer;
  end;

  TContract = record
    ContractID: PChar;
    CID: PGUID;
  end;

  TCategory = record
    Category: PChar;
    Entry: PChar;
    Value: PChar;
  end;

  TModule = record
    Version: UINT;
    CIDs: Pointer;
    Contracts: Pointer;
    Categories: Pointer;
    GetFactory: Pointer;
    Load: Pointer;
    Unload: Pointer;
  end;

  PModule = ^TModule;

var
  NSModule: PModule;

implementation

var
  mtModule: TModule;
  CIDs: array[0..1] of TCIDEntry;
  Contracts: array[0..1] of TContract;

function GetFileVersionResourceInfo(const FileName, VerValue: string): string;
var
  S: string;
  Value: Pointer;
  ValueSize: DWORD;
  VerInfoSize: DWORD;
  VersionInfo: Pointer;
  GetInfoSizeJunk: DWORD;
begin
  // retrieve the size of the version information resource
  VerInfoSize := GetFileVersionInfoSize(PChar(FileName), GetInfoSizeJunk);
  if VerInfoSize > 0 then
  begin
    // retrieve memory to hold the version resource
    GetMem(VersionInfo, VerInfoSize);
    try
      // retrieve the version resource
      if GetFileVersionInfo(PChar(FileName), 0, VerInfoSize, VersionInfo) then
        if VerQueryValue(VersionInfo, '\\VarFileInfo\\Translation', Value, ValueSize) then
        begin
          S := '\\StringFileInfo\\' +
          IntToHex(LoWord(LongInt(Value^)), 4) +
          IntToHex(HiWord(LongInt(Value^)), 4) + '\\';
          if VerQueryValue(VersionInfo, PChar(S + VerValue), Value, ValueSize) then Result := PChar(Value);
        end;
    finally
      FreeMem(VersionInfo, VerInfoSize);
    end;
  end;
end;

function GetVersion: Integer;
var
  I: Integer;
  sProductVersion: string;
  sModuleFileName: array[0..MAX_PATH] of Char;
begin
  Result := 1; // Firefox 4
  FillChar(sModuleFileName, MAX_PATH, 0);
  if GetModuleFileName(0, sModuleFileName, SizeOf(sModuleFileName)) > 0 then
  begin
    sProductVersion := Trim(GetFileVersionResourceInfo(sModuleFileName, 'ProductVersion'));
    if (sProductVersion <> '') and (sProductVersion[1] in ['4'..'9']) then
    begin
      // Firefox 4 = version 1
      // Firefox 5 = version 2
      // Firefox 6 = version 6
      // etc.
      I := StrToInt(sProductVersion[1]);
      if I <= 5 then
        Result := I - 3
      else
        Result := I;
    end;
  end;
end;

function MyConstructor(aOuter: nsISupports; const aIID: TGUID; out aResult): nsresult; cdecl;
begin

end;

initialization
  mtModule.Version := GetVersion;

  CIDs[0].CID := @Sample_CID;
  CIDs[0].ConstructorProc := @MyConstructor;
  mtModule.CIDs := @CIDs;

  Contracts[0].ContractID := Sample_CONTRACTID;
  Contracts[0].CID := @Sample_CID;
  mtModule.Contracts := @Contracts;

  NSModule := @mtModule;

end.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文