2010-09-04 17 views
11

J'essaie de créer une DLL compatible Gecko 2.0 dans Delphi.Exportation d'un symbole global à partir d'une DLL Delphi

Auparavant (avant Gecko 2.0), la DLL devait exporter une fonction NSGetModule(). Cela a fonctionné parfaitement.

À partir de Firefox 4, ma DLL est en cours de chargement (j'ai vérifié cela par un point d'arrêt dans ma section d'initialisation), mais ma fonction NSGetModule() n'est plus appelée. Ce comportement est conçu car à partir de Gecko 2.0 (Firefox 4), un composant binaire est pas censé exporter une fonction NSGetModule():

https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_2.0#Binary_components

Selon ces documents, ma DLL doit exporter un NSModule symbole de données qui pointe vers une structure. Dans la terminologie Delphi, je suppose qu'il s'agit d'une variable globale qui pointe vers un enregistrement Delphi.

En C++, voici comment exporter le symbole de données (global):

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

Ma question: comment puis-je y arriver à Delphes? Comment exporter une variable globale?

J'apprécie vos commentaires.

Répondre

14

Delphi exporte des variables globales de DLL d'une manière similaire à la façon dont elle exporte les fonctions:

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

Delphi peut importer des variables globales DLL, mais il est un peu un hack: déclarer une procédure d'importation de DLL du même nom le global à importer, puis obtenir l'adresse de la procédure et l'ajuster de manière appropriée. Les procédures importées par DLL, du point de vue de Delphi, sont des stubs qui effectuent un saut indirect dans la table d'importation de DLL. Les variables exportées sont liées par le chargeur de système d'exploitation en plaçant l'adresse du global exporté dans la table d'import, presque exactement comme les adresses des procédures exportées sont corrigées de la même manière.

Par exemple:

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

Bien sûr, ces derniers détails dépendent la mise en œuvre et la plate-forme, etc. Probablement une approche plus sûre est d'utiliser LoadLibrary avec GetProcAddress, qui renvoie l'adresse de la variable globale lorsqu'il est passé son prénom. Bien sûr, cela dépend aussi de la plateforme.

mise à jour 64 bits:

64 bits sous Windows, le code est légèrement différent. Les opcodes sont les mêmes, mais le mode d'adressage pour la même séquence d'instructions est différent; Au lieu d'un décalage absolu de 32 bits, il s'agit d'un décalage relatif de 32 bits.

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; 
+0

est-ce sécurisé? compatible avec 64bit? future version de Delphi? – FLICKER

2

En lisant les documents, je ne pense pas que Delphi permette l'exportation de variables globales directement car l'aide sur l'instruction exports ne traite que des routines. De plus il y a un très précis

Les variables globales déclarées dans une bibliothèque partagée ne peuvent pas être importés par une application Delphi .

et il est probablement sûr de supposer que si Delphi ne peut pas les importer, il ne les exportera pas non plus.

Je suppose que la façon de contourner cela pourrait être d'exporter une fonction qui renvoie un pointeur vers la variable globale ...

Quelque chose seul les lignes de:

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

var 
    _GlobalRecord: RGlobalRecord; 

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

exports GetGlobalRecord name 'ExternalNameOfGlobalRecord'; 

Ainsi, si la fonction NSGetModule renvoie la même structure que vous devez maintenant exporter en tant que variable globale. Vous pouvez essayer d'exporter cette fonction avec un nom tel que requis pour la variable globale à exporter:

exports NSGetModule name 'NSModule'; 
+2

Cette fonction va mettre une instruction mov avant l'adresse, et Gecko 2 attend probablement une STRUCT au adresse, pas de code ou un pointeur ... Peut-être que si vous définissez la fonction comme registre et avec un bloc asm vous pouvez alors utiliser dd, dw, db pour POKE les informations directement. C'est brut et a l'air horrible mais pourrait fonctionner. Je ne vois pas d'autre moyen. –

+0

@Ritsaert: Merci, je vais admettre volontiers n'avoir aucune expérience avec l'utilisation de DLL en langage croisé. Si vous avez un exemple, veuillez ajouter votre propre réponse et obtenir un représentant. –

+2

Je ne pense pas que cela fonctionnera. Le plus logiquement serait de prendre un compilateur avec un petit temps d'exécution (par exemple ansi C) qui peut exporter des variables, et importer la variable pascal en C (statique, pas de DLL), puis l'exporter de la DLL. Puis reliez le module pascal et le module C en une seule DLL. Free Pascal a CVAR et peut déclarer des variables externes, mais je ne sais pas si cela fonctionne avec les DLL. Quoi qu'il en soit, les variables exportées depuis les DLL ne sont pas officiellement supportées par MS (bien que de nombreuses DLL le fassent). Le mieux serait d'attirer l'attention de Gecko sur cela et de les faire changer en fonction. –

1

Voici ma solution Delphi. Et cela fonctionne même dans 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'; 

Maintenant, si vous pouvez me faire parvenir la mise en œuvre GenericClassInfo en delphi ce serait génial :)

0

Comme vous l'avez remarqué cela ne fonctionne pas dans 5 FF et FF 6. Au lieu de cela, vous pouvez ajouter un bloc d'initialisation pour vérifier la version de Firefox à l'exécution à ajuster mVersion en conséquence. Mozilla rompt intentionnellement les composants binaires, c'est donc une solution de contournement réalisable même entre différentes versions.

Vous pouvez utiliser Application.ExeName et 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;

0

Voici mon implémentation actuelle (cela fonctionne dans FF 5 et FF 6 et probablement tous les autres à l'avenir)

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.