views:

213

answers:

1

We have our own glue-layer-code-thingamajig that allows us to host the .NET runtime in our Win32 Delphi program. This has allowed us to do a gradual transition to .NET over time.

But, we have some problems with it from time to time, and yesterday I saw an answer here on SO that referred to Jcl's .NET host implementation, so I thought I'd take a look to see if there's some obvious differences.

Turns out there is, but I don't understand what it does, why, and whether I need to do the same. I'll certainly try it, but I'd very much like for someone else that understands the reason behind this odd code to tell me what it does.

In time, we might switch to using the Jcl implementation, but since we have an impending release, unless it's absolutely necessary in order to fix the current problems, then a major overhaul at this level of the code is not justified, so please don't just suggest we switch.

Anyway, the difference revolves around how they call into the .NET functions to load and bind to the .NET runtime, basically how they call the exported functions from the .NET dll.

Here's my code:

type
  TCorBindToRuntimeEx = function(pwszVersion: PWideChar;
    pwszBuildFlavor: PWideChar;
    startupFlags: DWord; rclsid, riid: PGUID;
    out ppv: IUnknown): Integer; stdcall;
...
var
  CorBindToRuntimeEx  : TCorBindtoRuntimeEx = nil;
...
CorBindToRuntimeEx := GetProcAddress(Runtimehandle, 'CorBindToRuntimeEx');
...
clsid := CLASS_CorRuntimeHost;
iid := IID_ICorRuntimeHost;
rc := CorBindToRuntimeEx('v2.0.50727', 'wks', 0, @clsid,
    @iid, UnkRuntimeEngine);

Now, here I simply use GetProcAddress to load the address of the exported function into a variable, typed to be a stdcall function pointer, and then I call it. This works, sort of. As I said, some problems with odd error messages in a few cases.

Ok, here's their code, and pay particular attention to the function with the assembler code.

function CorBindToRuntimeEx(pwszVersion, pwszBuildFlavor: PWideChar;
  startupFlags: DWORD; const rclsid: TCLSID; const riid: TIID;
  out pv): HRESULT; stdcall;
{$EXTERNALSYM CorBindToRuntimeEx}
...
var
  _CorBindToRuntimeEx: Pointer = nil;

function CorBindToRuntimeEx;
begin
  GetProcedureAddress(_CorBindToRuntimeEx, mscoree_dll,
    'CorBindToRuntimeEx');
  asm
    mov esp, ebp
    pop ebp
    jmp [_CorBindToRuntimeEx]
  end;
end;
...
OleCheck(CorBindToRuntimeEx(PWideCharOrNil(ClrVer),
  PWideChar(ClrHostFlavorNames[Flavor]), Flags,
  CLASS_CorRuntimeHost, IID_ICorRuntimeHost,
  FDefaultInterface));

Note that I've reformatted the code slightly to avoid horizontal scrollbars here on SO, but only to add a few linebreaks and some indentation, the code is as-is.

The final call is probably irrelevant, it's basically going to pass the same parameters that we do (note that we pass 0 as the options value, but we've also tried with the same specific arguments the Jcl code uses, and the problems are still present).

So, my question is, what the he** does the assembler code do? I know what it does in the technical sense, I've been programming assembly before, so it manipulates the stack pointers.

The question is why does it have to do this. I just don't get it.

Could it be that the stack-frames are not quite stdcall after all?

Please teach me something today.


Edit: Ok, changed my code accordingly, but the problem we have still exists, so that wasn't it. Looks like I'll be doing some WinDbg digging into third-party code after all.

+5  A: 

The assembler code removes CorBindToRuntimeEx's stackframe. If you call CorBindToRuntimeEx all parameters are pushed to the stack (=> stdcall). The function then calls GetProcedureAddress to initialize the global _CorBindToRuntimeEx variable that now points to the 'CorBindToRuntimeEx' function.

After GetProcedureAddress returns the _CorBindToRuntimeEx function must be called. But here we have a problem. Delphi automatically added a "push ebp; mov ebp,esp" to the code (where the "begin" is). And in order to remove that stackframe the "mov esp,ebp; pop ebp" is used. The "jmp [_CorBindToRuntimeEx]" then sets the execution pointer to the _CorBindToRuntimeEx function which then uses the return address from our CorBindToRuntimeEx function.

Andreas Hausladen
So basically what you're saying is that since they put the call to GetProcedureAddress into the function, they need to clean up after calling it, so if I just drop the whole function and call it through a variable with a function pointer in it, then I can just forget about this whole thing?
Lasse V. Karlsen
In other words, my code should be fine, if I understand you correctly.
Lasse V. Karlsen
Yes, your could should be fine.
Andreas Hausladen