从Delphi DLL导出全局符号

时间:2010-09-04 11:06:20

标签: delphi dll export symbols

我正在尝试在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中实现这一目标?如何导出全局变量?

感谢您的反馈意见。

5 个答案:

答案 0 :(得分:14)

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

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

Delphi可以从DLL导入全局变量,但这有点像黑客攻击:声明一个与要导入的全局同名的DLL导入过程,然后获取过程的地址并适当地调整它。从Delphi的角度来看,DLL导入的程序是通过DLL导入表间接跳转的存根。通过将导出的全局地址放入导入表中,导出的变量由OS加载程序链接,几乎与导出过程的地址类似地修补的方式相同。

例如:

{$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;

答案 1 :(得分:2)

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

  

在共享中声明的全局变量   库不能由Delphi导入   应用

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

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

单独的东西:

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

var
  _GlobalRecord: RGlobalRecord;

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

exports GetGlobalRecord name 'ExternalNameOfGlobalRecord';

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

exports NSGetModule name 'NSModule';

答案 2 :(得分:1)

这是我的Delphi解决方案。它甚至可以在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实现,那将是非常棒的:)

答案 3 :(得分:0)

正如您所注意到的,这在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 - mVersion:= 6;

FF 7 - mVersion:= 7;

答案 4 :(得分:0)

这是我当前的实现(这适用于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.