Экспорт глобального символа из DLL-библиотеки Delphi - PullRequest
11 голосов
/ 04 сентября 2010

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

Ранее (до 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 ]

14 голосов
/ 07 сентября 2010

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 голосов
/ 04 сентября 2010

Читая документы, я не думаю, что 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';
1 голос
/ 19 сентября 2010

Вот мое решение 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, это было бы здорово

0 голосов
/ 13 июля 2011

Вот моя текущая реализация (это работает в 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.
0 голосов
/ 12 июля 2011

Как вы заметили, это не работает в 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;

...