【问题标题】:Export a global symbol from a Delphi DLL从 Delphi DLL 导出全局符号
【发布时间】:2010-09-04 11:06:19
【问题描述】:

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

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

从 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 中实现这一点?如何导出全局变量?

感谢您的反馈。

【问题讨论】:

    标签: delphi dll export symbols


    【解决方案1】:

    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.
    

    当然,后面的细节取决于实现和平台等。可能更安全的方法是将LoadLibrary 与GetProcAddress 一起使用,这将在传递其名称时返回全局变量的地址。当然,这也取决于平台。

    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;
    

    【讨论】:

    • 这样安全吗?兼容64位? Delphi 的未来版本?
    【解决方案2】:

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

    在共享中声明的全局变量 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';
    

    【讨论】:

    • 此函数将在地址前放置一条 mov 指令,而 Gecko 2 可能希望该地址处有一个 STRUCT,而不是代码或指针...也许如果您将该函数定义为寄存器并使用 asm块然后您可以使用 dd, dw, db 直接 POKE 信息。它很粗糙,看起来很糟糕,但可能会起作用。我也没有看到任何其他方式。
    • @Ritsaert:谢谢,我承认没有任何跨语言 dll 使用经验。如果您有示例,请添加您自己的答案并获得一些代表。
    • 我不认为这会奏效。最合乎逻辑的做法是采用一个可以导出变量的小型运行时(例如 ansi C)的编译器,并将 pascal 变量导入 C(静态,无 DLL),然后从 DLL 导出它。然后将 pascal 和 C 模块链接到一个 DLL 中。 Free Pascal 有 CVAR 并且可以声明外部变量,但我不知道这是否适用于 DLL。无论如何,MS 不正式支持从 DLL 导出变量的 afaik(尽管许多 DLL 支持)。最好的办法是让 Gecko 注意到这一点,然后让他们把它变成一个函数。
    • @Marco:谢谢。如果您有建议的示例,请添加您的答案(我不自豪:),无论如何也不要太自豪)。如果你这样做了,请特别注意MS不支持的部分......我认为这是非常重要的信息。
    • 这里描述的技术行不通。可以工作的是具有足够“dd”或类似数据条目的 asm 块来填充结构的预期长度,以及 DLL 主开始/结束中的 VirtualProtect 以使包含结构的代码页可写。但是没有必要,因为Delphi支持直接导出变量(虽然我不知道这个功能是什么时候引入的)。
    【解决方案3】:

    这是我的 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 实现发送给我,那就太棒了:)

    【讨论】:

      【解决方案4】:

      您已经注意到这在 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;

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-15
          • 1970-01-01
          • 1970-01-01
          • 2013-04-10
          相关资源
          最近更新 更多