views:

182

answers:

3

I'm trying to create a Gecko 2.0-compatible DLL in Delphi.

Previously (pre-Gecko 2.0) the DLL needed to export a NSGetModule() function. This worked flawlessly.

Starting with Firefox 4, my DLL is getting loaded (I have verified this though a breakpoint in my initialization section), but my NSGetModule() function does not get called anymore. This is the designed behavior because starting with Gecko 2.0 (Firefox 4), a binary component is not supposed to export a NSGetModule() function:

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

According to these docs, my DLL needs to export a NSModule data symbol which points to a struct. In Delphi terminology, I assume this is a global variable which points to a Delphi record.

In C++, this is how you export the (global) data symbol:

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

My question: how do I accomplish this in Delphi? How do I export a global variable?

Thanks in advance. I appreciate your feedback.

+2  A: 

Reading the docs, I don't think Delphi allows for the export of global variables directly as the help on the exports statement discusses only routines. Also there is a very definite

Global variables declared in a shared library cannot be imported by a Delphi application.

and it is probably safe to assume that if Delphi can't import them, it also won't export them.

I suppose the way around this could be to export a function that returns a pointer to the global variable...

Something alone the lines of:

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

var
  _GlobalRecord: RGlobalRecord;

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

exports GetGlobalRecord name 'ExternalNameOfGlobalRecord';

So, if the NSGetModule function returns the same struct as you are now required to export as a global variable, you could try to export that function with a name as required for the global var to be exported:

exports NSGetModule name 'NSModule';
Marjan Venema
This function will put a mov instruction before the address, and Gecko 2 probably expects a STRUCT at the address, not code or a pointer... Perhaps if you define the function as register and with an asm block you could then use dd, dw, db to POKE the information directly. It's crude and looks awful but might work. I also do not see any other way.
Ritsaert Hornstra
@Ritsaert: Thanks, I'll readily admit not having any experience with cross language dll use. If you have an example, please add your own answer and get some rep.
Marjan Venema
I don;t think this will work. Most logically would be to take a compiler with a small runtime (e.g. ansi C) that CAN export variables, and import the pascal variable into C (static, no DLL), and then export it from the DLL. Then link both the pascal and C module into one DLL. Free Pascal has CVAR and can declare external vars, but I don't know if this works with DLLs. Anyway, afaik exporting variables from DLLs is not officially supported by MS (though many DLLs do). Best would be to draw Gecko's attention to this and have them change it into a function.
Marco van de Voort
@Marco: Thanks. If you have an example for your suggestion, please add your answer (I'm not proud :), well not too proud anyway). And if you do, please draw extra attention to the part of what MS does not support... I think it is really important information.
Marjan Venema
Marjan: How many export examples do you know in the standard headers, e.g. the Platform SDK? If I had readily made code, I'd answered, not commented. Note that Ritsaert is right, and your answer can't work.
Marco van de Voort
@Marco, hey, no worries, I wasn't griping about your comment, though I see now how you could have taken my words as such. I just wanted to encourage extra information for the OP as the second part of my answer was a stab in the semi-dark from the start...
Marjan Venema
The technique described here wouldn't work. What could work is an asm block with sufficient 'dd' or similar data entries to fill out the expected length of the structure, along with a VirtualProtect in the DLL main begin/end to make the code page containing the structure writeable. But it's not necessary since Delphi supports exporting variables directly (though I don't know when the feature was introduced).
Barry Kelly
@Barry: Thanks. The docs I read originally were D2009's. D2010 doesn't even have `exports` in the index. The D2010 docwiki on the exports statements also does not mention exporting variables... If it has been available earlier than Delphi XE, it has been pretty well hidden :-)
Marjan Venema
+8  A: 

Delphi exports global variables from DLLs in a similar way to how it exports functions:

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

Delphi can import global variables from DLLs, but it's a bit of a hack: declare a DLL import procedure of the same name as the global to import, then get the address of the procedure and adjust it appropriately. DLL imported procedures, from Delphi's perspective, are stubs that do an indirect jump through the DLL import table. Exported variables are linked by the OS loader by putting the address of the exported global in the import table, almost exactly like how addresses of exported procedures are similarly patched in.

For example:

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

Of course, the latter details are implementation and platform dependent etc. Probably a safer approach is to use LoadLibrary with GetProcAddress, which will return the address of the global variable when passed its name. Of course, that's also platform dependent.

Barry Kelly
A: 

Here is my Delphi solution. And it works even in 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';

Now if you can send me GenericClassInfo implementation in delphi that would be awesome :)

Matej Spiller-Muys