Экспортировать глобальный символ из 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.