Экспортировать глобальный символ из DLL Delphi

Я пытаюсь создать Gcko 2.0-совместимую DLL в Delphi.

Ранее (pre-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.

В С++ вы экспортируете (глобальный) символ данных:

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

Мой вопрос: как это сделать в Delphi? Как экспортировать глобальную переменную?

Я ценю ваши отзывы.

Ответ 1

Delphi экспортирует глобальные переменные из библиотек DLL аналогично тому, как он экспортирует функции:

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

Delphi может импортировать глобальные переменные из DLL, но это немного взломать: объявить процедуру импорта DLL с тем же именем, что и глобальная для импорта, затем получить адрес процедуры и соответствующим образом скорректировать ее. DLL-импортированные процедуры, с точки зрения Delphi, являются заглушками, которые выполняют косвенный переход через таблицу импорта 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.

Конечно, последние детали зависят от реализации и платформы и т.д. Вероятно, более безопасный подход заключается в использовании LoadLibrary с GetProcAddress, который вернет адрес глобальной переменной при ее имени. Конечно, это также зависит от платформы.

64-разрядное обновление:

В 64-разрядной версии Windows код немного отличается. Коды операций одинаковы, но режим адресации для одной и той же последовательности команд отличается; вместо 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;

Ответ 2

Чтение документов, я не думаю, что Delphi позволяет напрямую экспортировать глобальные переменные, так как помощь в инструкции экспорта обсуждает только подпрограммы. Также существует очень определенная

Глобальные переменные, объявленные в общей библиотека не может быть импортирована 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';

Ответ 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';

Теперь, если вы можете отправить мне реализацию GenericClassInfo в delphi, это было бы потрясающе:)

Ответ 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.