Link to home
Start Free TrialLog in
Avatar of Wim ten Brink
Wim ten BrinkFlag for Netherlands

asked on

EXE as DLL (2)

Okay, continuing question https://www.experts-exchange.com/questions/21142297/EXE-as-DLL.html here. I have two executables:

program EXE;
uses Windows;
{$APPTYPE CONSOLE}
type TSum = function( A, B: Integer ): Integer;
var
  A, B: Integer;
  Handle: THandle;
  Sum: TSum;
begin
  Randomize;
  Handle := LoadLibrary( Pchar( 'DLL.exe' ) );
  if ( Handle > 0 ) then begin
    Sum := TSum( GetProcAddress( Handle, 'Sum' ) );
    if Assigned( Sum ) then begin
      A := Random( 10 ) + 1;
      B := Random( 10 ) + 1;
      WriteLn( A, ' + ', B, ' = ', Sum( A, B ) );
    end
    else begin
      WriteLn( 'Failed to find the sum routine.' );
    end;
    CloseHandle( Handle );
  end
  else begin
    WriteLn( 'Failed to load DLL.' );
  end;
  ReadLn;
end.

Above executable is loading another executable, thus "abusing" it as if it's a DLL. Now the code for this other executable.

program DLL;
uses Windows, untInit in 'untInit.pas';
function Sum( A, B: Integer ): Integer;
begin
  Result := A + B;
end;
exports Sum;
begin
  MessageBox( GetDesktopWindow, 'Here we go', 'DLL', MB_OK );
end.

Oh, it uses an unit. Here's the code for the unit:

unit untInit;
interface
uses Windows;
implementation
initialization
  MessageBox( GetDesktopWindow, 'Hello', 'DLL', MB_OK );
finalization
  MessageBox( GetDesktopWindow, 'Goodbye', 'DLL', MB_OK );
end.

Okay. DLL.EXE will run perfectly as a stand-alone executable. And EXE.EXE will load DLL.EXE and call the exported method too. However, there are a few flaws. (Otherwise I would not be asking this.)

The problem is simple. When I load the executable as a DLL, it doesn't initialize anything. None of the messageboxes will show, thus nothing within the DLL is initialized. Not even the global system variables. And this makes it quite difficult for me to use this technique for what I've planned.

DLL.EXE will be a simple console application that analyzes a bunch of data and then return a summary. Since it's a console application, it will just display this summary to the console before it closes again. My second application wants to use the same summary information but while I could let it grab the summary from the console, I want a more direct connection. I could use inter-process communication techniques and it would not be too difficult for me, except that I think it just complicates things too much. I could also put the summary calculator in a separate DLL but it just means that I need to create a third project.

I'm not in a hurry with this, though. This is just an experiment. As long as I don't have a working solution, I just work with the DLL option, having a console and a Forms application load this DLL, execute the method and then free the DLL again. I don't want any other alternative solutions either. All I want is to load one executable in the process space of another executable, have it initialized correctly so it operates as a normal Delphi DLL and get it all to work nicely.

And for those who wonder how I will prevent the DLL to execute like an executable when I load it as a DLL, well, simple. That's what you use mutexes for. Set a mutex in the executable and if the DLL tries to set it too, it notices it is already in use, thus it knows it should behave like a DLL. That is, if it ever gets initialized...

It's not urgent but still extremely difficult and educational. Answers like "It cannot be done" will not be rewarded.
Avatar of jturpin
jturpin
Flag of United States of America image

Try adding another routine:

procedure SetHandles(ThisApp:Pointer);
begin
  Application:= TApplication(ThisApp);
end;

Then after you have called LoadLibrary just call SetApp before attempting to call the Sum routine.
Avatar of Wim ten Brink

ASKER

@jturpin, I'm not using the Forms unit thus TApplication is completely unknown in both the DLL and the executable. I'm not planning to use forms either. The problem isn't initializing the TApplication object. The problem is initializing whatever the Systems unit needs to initialize...
ASKER CERTIFIED SOLUTION
Avatar of Madshi
Madshi

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of BlackTigerX
BlackTigerX

Then why don't you use Mutexes for the purpose of initializing only the first time the method (Sum in this case) is called?

function Sum( A, B: Integer ): Integer;
begin
  if not (MutexExists) then
    InitializationCodeHere; //and create the Mutex
  Result := A + B;
end;
P.S: Perhaps you should put the real stuff into a real dll.dll and just write a wrapper dll.exe, which loads dll.dll and outputs the result to the command line.
Let's see. If I create a normal DLL and include a unit to it with an initialization/finalizations section, those sections would be executed, no matter what happens. However, not even the initialization parts are executed when I load the executable as a DLL.
And yes, I need to get the code initialized in some way anyway. It could be done easier than BlackTigerX says, I guess, by using a variable that is initialized with a value. Something like:

var IsnotInitialized: Boolean = True;

function Sum(A, B: Integer): Integer;
begin
  if IsnotInitialized then begin
    IsnotInitialized := False;
    InitializeSystem;
  end;
  Result := A + B;
end;

My only problem is how to initialize the system unit and call the initialization sections of all units. I hope that once I started the Initializations, Delphi would know how to uninitialize them again.

If you compare how DLL's and Executables are made in Delphi, you see a lot of things they both have in common. Both will initialize and finalize every unit. Both will run the code in the begin-end block of the project. So where's the precise difference between them and how to fool the system to load an executable as a DLL. (Or, reverse psychology: how to run a DLL without a host?)

I know it's quite difficult, but is it impossible?
(Okay, there are a lot easier solutions too. But this is a learning challenge, not some project with a real deadline.)
>> Both will run the code in the begin-end block of the project.

And as I said, the begin-end block of an exe-project ends with a (hidden) ExitProcess call. So even if you could convince your dll.exe to get initialized when being loaded as a dll, it would help you, since then your whole process would close down, as soon as the initialization of dll.exe is done.
I meant "..., it would *NOT* help you,...", of course.
:-)
Okay, true... If I would get the main DPR code to execute, it would close my project. But what about if I only initialize the units that my DLL.EXE is using without calling the main dpr code? Isn't there a way to do this? I know, it would be a real hell to find out the correct order to run them but impossible?
The other problem is, of course, telling Windows where the entrypoint of my DLL.EXE is, if I load it as a DLL. This is required because at some point, the system needs to know when to start initializing. But for now this could be done by just exporting a simple INIT function from the executable. If it's called, I know I need to initialize every unit.

The dpr itself is NOT a unit thus if I just call all initialization sections, it would just be skipped. (Thus, in above code I would see 3 messageboxes if run as exe, or 2 if run as a dll.)

Do you have any suggestions to do this then? ;-)

And no, I don't believe in Impossible... :-)
Well, check out "SysInit._InitExe". It does a bit work, then it calls "System._StartExe", which again does a bit work and then calls "System.InitUnits". Perhaps you could simply call "SysInit._InitExe". You can get the address by using this code:

function GetInitExeAddr : pointer;
asm
  mov eax, offset SysInit.@InitExe
end;

But it's quite difficult to give in the correct parameters when calling InitExe.
Hmm... SysInit. I should have looked at that sooner.

Don't you mean _InitLib instead? It should initialize as a DLL, shouldn't it?

This procedure would probably of this (C++) format:
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);

So probably in Delphi:
function _InitLib(Hinst: Longint; Reason: Integer; Resvd: Pointer): Longbool; stdcall;

The comments also say so, although they also mention an inittable that needs to be passed in the EAX register. And _InitExe requires a pointer to that table too. Nasty, since I don't know where to find it... Both methods don't seem to differ much, though.

And now the really bad news... The SysInit is well-protected by Delphi. You cannot add it to your uses clause since it's already used like the Systems unit. But you cannot call any of it's methods either because... Well, you just can't... The Delphi editor doesn't even like it when you try to open this unit in the editor either. So I need to work around it.
Well, basically it just sets a value to HInstance global variable and sets some value in a table Module variable that's unfortunately declared in the implementation section. So again, a dead end.

And then I discovered the VclInit function... :-)
And it fails too. Apparantly not even the data segment for my variables has been reserved. It fails on "IsLibrary := isDLL;" and basically, it crashes without even generating an exception. I'd almost start using the MadExcept components if it would help but no, they would not even be initialized before this error occurs...

Trying to typecast DllProc and calling it doesn't work either. The compiler has stripped away this variable since it's not a DLL, of course. :-( Borland sure makes things difficult for me...

Apparantly when the Delphi compiler creates a DLL or EXE, it strips away some parts from the system and sysinit units that could be used to use it the other way around. And that's a real shame. If this is true, there is no good work-around for this because I would never be able to set up the system variables correctly.

I have seen an executable once that could be loaded as a DLL once. But that was over 6 years ago and it probably wasn't written in Delphi either. (Maybe not even 32-bits.) A real shame if there's just no way to load one executable in the address space of an existing process with Delphi.
*gasp* does that mean... you have to start to believe in the word Impossible, sometimes? ;-)
Nope... :-) Impossible is just impossible. :-P

It's just that matters are just a bit more complicated than I expected. The proof of concept already exist. I can load an executable and execute a method from this second executable. It's just that the system is lacking the information to set up the system correctly. Apparantly no memory is reserved for the data in the executable, and no global variable has been initialized. Actually, nothing is done to set up the system correctly after my executable is loaded as DLL. Just means I have to examine more closely what happens when an application loads and unloads a DLL. Very educational, though. :-)
The biggest enemy I face is the fact that the Delphi compiler just strips away the functions that I could use to pretend it's a DLL. So, the big question is how to get around this mess... (Without using real dirty tricks.)
I'm not sure whether calling InitExe or InitLib would be better. InitLib might fail because Delphi compiled the project as an exe. InitExe should work, it does initialize all units.

You could  probably force Delphi to not link InitLib out by adding this function to your code (and by using it in your code):

function GetInitLibAddr : pointer;
asm
  mov eax, offset SysInit.@InitLib
end;
Yes, I've added that GetInitLibAddr but think Delphi just ignores it and strips it away anyway since it's not a DLL. I could try it with InitExe again, of course. Just wondering what parameters I need to pass...
Am at home now so have Delphi 7 available. (Only Delphi 5 at work) And guess what? In the Delphi 7 source I can see the required parameters for these functions! :-)

procedure _InitExe(InitTable: Pointer);

Now the problem... How do I feed this procedure with the correct value? The DLL itself has nothing initialized, not even the global variables...
perhaps what you need is
"a .dll that can be loaded as .exe", instead of a ".exe that can be loaded as .dll"...

rundll32.exe... can be ran from the command line... you forget about the initialization, finalization problem...

what about that option?

haven't tried anything, just throwing one more thought in, see what you think about that
>> Delphi just ignores it and strips it away anyway

Did you actually try my suggestion? Evidently not...   :-(

function GetInitLibAddr : pointer;
asm
  mov eax, offset SysInit.@InitLib
end;

initialization
  MessageBox(0, pchar(IntToHex(dword(GetInitLibAddr), 1)), 'info', 0);
  ExitProcess(0);

Set a breakpoint on ExitProcess(0) and run the code. When the breakpoint is reached, open the CPU view and jump to the address that was displayed in the message box. InitLib is there.
@BlackTigerX: I know rundll32.exe would be an option, but it's an ALTERNATIVE solution. I'm not trying to get a solution for some problem, I'm trying to see if it is possible with Delphi! :-)

@Madshi, I have tried this:

type
  PDllProc = ^TDllProc;
  TDllProc = function( Hinst: Integer; Reason: Integer; Resvd: Pointer ): LongBool; stdcall;
function GetInitLibAddr: PDllProc;
asm
  mov eax, offset SysInit.@InitLib
end;
procedure Init( Hinst: Longint ); stdcall;
var
  DllProc: PDllProc;
begin
  DllProc := GetInitLibAddr;
  DllProc^( Hinst, DLL_PROCESS_ATTACH, nil );
end;
exports Init;

However, the compiler decided to start puking then I reach the function call. I do get an address, though. But even a "Trace into" in the CPU window will get me in that procedure. The only place where I end up is at position nil, from where it tries to continue to run... The assembler code at this point is:

push $00 // nil
push $01 // DLL_PROCESS_ATTACH
mov eax, [ebp+$08] // Should be the parameter.
push eax
mov eax, [epb-$04]
call dword ptr [eax]
And the next step is at address $00000000 for some weird reason. Well, eax equals $00404474 and at this address the value is nil... Thus, there isn't even any code at this location. As I said, it is stripped away by the compiler during the optimization and even with optimization turned off, it is stripped away...

I've tried the same with the InitExe method but here too the same problem. It calls:

mov eax, [epb-$04]
call dword ptr [eax]

And eax has the value $00404320 now. But this address too is just assigned nil. Thus the system crashes since this "code" cannot be executed.

I did find the address of InitExe, btw. I had to scroll up in the CPU window and this time it was at $00BA4320... So it seems I have to increase the address that I receive with some value. ($007A0000) but where does it come from? InitLib should be located just before this one but the compiled code cannot be found. It's not in there anywhere. InitLib isn't there but InitExe is. But not at the address we've expected.
This might be related to the compiler-magic that Delphi has. InitExe is one of the special system functions.

I finally ended up calling this:

  asm
    mov eax, offset SysInit.@InitExe
    add eax, $007A0000
    call eax
  end;

And that did get me in the InitExe method. :-)
But then it fails because it calls the function GetModuleHandle and here too, the addresses are incorrect. (And again, $007A0000 bytes away from their real position.)

So now I have to try and correct those addresses... How to tell the DLL that all adresses have moved to a new location? Nasty...
But InitLib is completely stripped away, btw. There's not a byte to be found of this function in the compiled executable.
Seemingly the function address returned by "GetInitLibAddr" is not relocated. That's a bit strange. But we can work around that, of course. I think this one should work:

procedure Init(Context: pointer); stdcall;

  function GetInitLibAddr: PDllProc;
  asm
    mov eax, offset SysInit.@InitLib
  end;

var InitLib : procedure (Context: pointer);
begin
  InitLib := pointer(dword(GetInitLibAddr) - $400000 + HInstance);
  InitLib(Context);
end;

exports Init;

Now please trust me - InitLib is *NOT* stripped away, if you actually use it like in the code shown above...   :-)
Madshi, I looked in the CPU window. I see the ASM code of the InitExe code and I see the ASM code of InitializeModule which is a procedure before the InitExe method in the sourcecode. I also see linenumbers. In Delphi 5, I see a huge gap in linenumbers from line 192 (end of InitializeModule) to line 295 (Start of InitExe). Thus, for some reason the compiler put the code for InitLib at some completely different location or it's just not in there... Actually, UninitializeModule and VclInit and VclExit and _InitPkg have also been stripped away by the compiler. And I don't know of any reason why Delphi would put the order of functions in the compiled binary in a completely different order than they appear in the source.

Another problem is the offset of the InitExe procedure. HInstance from the executable is $00400000 and by adding $400000 to it, I get $00800000 but the real distance is $007A0000. About $050000 less. But as I said, I can call InitExe already. But all other functions in the executable are also located to the wrong address. The ImageBase option that can be configured seems to be related to this, though. If I change it to $00800000 I only have to add $003A0000, thus $00400000 less.
But it's not just InitExe that is located to a new address AFTER the executable is loaded. Several other functions in the DLL seem to have been relocated.

And it's that darned optimizer again, that strips away anything you don't use. (Even if you tell it not to optimize the code.) I changed my code to this:

function GetInitLibAddr: PDllProc; stdcall;
asm
  mov eax, offset SysInit.@InitLib
  add eax, $007A0000
end;

procedure Init( Hinst: Longint; Reason: Integer; Resvd: Pointer ); stdcall;
var
  DllProc: PDllProc;
begin
  DllProc := GetInitLibAddr;
  asm
    mov eax, DllProc
    call eax
  end;
  MessageBox( GetDesktopWindow, 'Wow', 'DLL', MB_OK );
end;
exports Init;

So now, InitLib is included in my source. And indeed, it is at the right address too. But now the InitLib function itself crashes because it too has the wrong addresses stored in it's memory. (GetInitLibAddr is declared stdcall, btw, else the parameter would be passed in eax, which I happen to alter...) InitLib is now called correctly, I hope. It will retrieve the parameters from the stack since ebp is pointing to their location. (And ebp isn't changed when calling InitLib.)

If I step through the CPU window, the function seems to fail on the GetModuleName method. However, if I change the ImageBase, it fails as soon as I try to assign a value to a global variable. Thus, I believe that before I call InitLib, I need to call yet another function that handles the initialization. Looking at the sourcecode of SysInit, this function must be called from somewhere else. But there's nothing in the Delphi sourcefiles that even provides a small hint for this.

Fun, though. I only have to change the DLL.EXE project header from "program DLL;" to "Library DLL;" and it suddenly works fine. :-)
>> GetInitLibAddr is declared stdcall, btw, else the parameter would be passed in eax, which I happen to alter...

That doesn't make any sense to me. GetInitLibAddr has no parameters, so it doesn't make any difference which calling convention you're using!

>> InitLib is now called correctly, I hope

No, it's not. You're calling InitLib without giving it the needed parameters. You don't need that asm stuff in your "Init" function. Declare DllProc as "TDllProc", not as "PDllProc", then you can directly call it  and give in the correct parameters.

It might be, though, that the whole file is not *able* to be relocated at all. In that case you can do what you want and you'll find no way to make this all work.
Oh, oops. I added a parameter for the value that had to be added or subtracted, typed the comment, then removed the parameter again... :-) That's what you get when you type comments while coding. ;-)

Actually, InitLib should NOT be called with any parameters since it looks to the parameters that are passed to the calling procedure. Basically, the Init procedure stores three parameters on the stack. If I'm going to call InitLib with the same parameters, I need to push them to the stack, then adjust ebp to have the new esp value so it can find the parameters relative from ebp and when the call returns I must restore the ebp value again. Thus:

  asm
    push ebp
    push Resvd
    push Reason
    push Hinst
    mov ebp, esp
    mov eax, DllProc
    call eax
    pop ebp
  end;

And even after that, there will be three more items on the stack that need to be popped. Normally, a Delphi method will push ebp and adjust it to look better at the stack. (Making adresses relative from ebp.) Means esp may change to whatever you like but it still knows where to find the local variables on the stack. At the end of the method, the method pops the old ebp value back.
But the InitLib method doesn't save and restore the ebp value, thus it's not looking for it's own local variables. It "steals" the local variables from the method that is calling it. I've checked how a library calls the InitLib function. It doesn't push any values to the stack either. No parameters seem to be used. InitLib even seems to expect one value to be passed to it in the eax field...

This is quite educational, though. :-)

There seems to be some table at the beginning of my compiled dpr, though. (If I compile it with the library header, though.) It has these values:
05 00 00 00 14 46 BA 00 B4 44 BA 00 84 44 BA 00 80 41 BA 00 18 41 BA 00 FC 44 BA 00 CC 44 BA 00 9C 45 BA 00 48 45 BA 00 00 00 00 00 E4 45 BA 00

I've displayed it this way since it shows this is some kind of table. It's exactly at the start of my DLL project. (Compiled) The values shown are addresses somewhere in the SysInit unit. The same table appears if I compile the project with the "program" header, although the values are a lot different. (But again, 5 items.) An important question is, of course, how I can find the start addresses of my dpr or units.
Well, there's a table somewhere which has all units in it including initialization and finalization address. Probably that's what you just stumbled across.
Could be, yes. But 5 units? (Assuming the first number is a counter.) Well, Windows of course. System and SysInit. My own untInit unit. So the fifth one? SysConst would be most likely but I don't see any unit referring to it. No, wait. The project itself, of course. :-)
Now, this table is what I need, of course. It tells me where to start all the initializations. Only problem now is how to find it in an environment where nothing is initialized...

Analyzing it:
00000005 00BA4614 // Unit count and pointer to address below.
00BA44B4 00BA4484 // SysInit initialization & Finalization
00BA4180 00BA4118 // System initialization & Finalization
00BA44FC 00BA44CC // Windows initialization & Finalization
00BA459C 00BA4548 // untInit initialization & Finalization
00000000 00BA45E4 // Project initialization and Finalization.

Now, if only I could know where to get this address then I could implement my own initialization. I found it through the CPU window but how to determine it in a running application?
And I wonder why most units appear to have both an initialization part and a finalization part... Oh, well.. It did suprise me to see that Delphi just considers the DPR to be just a DLL, with no initialization part and quite interesting, with a finalization part! (This last part generates those exceptions that you can't find when you close your application!) :-)

Problem is, Delphi seems to have hardcoded the address of this table in the DLL or EXE. How to retrieve it in an uninitialized environment, though? (Then I can fake the initialization part for the DLL.) Problem is, if I add a global variable, the table moves to another location... I feel I'm pretty close to a solution, though...
Ever found this table with your MadExcept components, Madshi?
Sure, madExcept knows how to find it...   :-)
It does it by disassembling the entry point of the exe/dll. There somewhere you'll find this:

mov eax, xxxxxxxx
call @InitExe/Lib

The "xxxxxxxx" is the address to this table.
Well, the only problem is that somehow, the EXE must find it's own entry point once it is loaded as a DLL. MadExcept won't work here since not even the MadExcept unit initialized yet. Could the calling executable somehow get the entry point of the DLL? If so, then it might just pass that entrypoint on to the loaded executable...
>> Could the calling executable somehow get the entry point of the DLL?

Sure, simply parse the PE file headers. See the function "GetImageNtHeaders" in madTools.pas. The returned structure directly contains the offset (to the dll handle) of the entry point.
Hmmm... Think I'll finally have to use a copy of the MadComponent set now. :-)
I'll have a look at it soon. At least I understand a bit more about how Delphi loads DLL's and executables. But I wonder if an executable has a DLL entry point...
Well, GetImageNtHeaders is only a little function. If you don't want to use madCollection, you can copy the code from there.
I'd have to download MadCollections first. :-)
I've advised quite a few people about your component set but I myself hardly ever use it. Last time I used it was...
...
...
..
Hmmm. Can't remember but I think about a year ago, or so. :o)
Well, I ran out of play-time so I have to stop trying to get this to work, but I learned a lot from it.

jturpin, didn't read the question correctly, no points.
BlackTigerX, interesting concept but not helpful. Rundll32.exe is not a solution but an alternate solution.
DragonSlayer, I still don't think it's impossible. Just too hard at this moment. :-)
Madshi, Lots of good remarks, helped me to get very close to a possible solution.