Further investigation showed that the assertions wasn't firing using the Delphi host because of some incorrect configuration files. Once that was solved, the Delphi host died just as dramatically as the C# host.
Please note that all this is tied to XE2 64 bit builds. We already noticed that assertions kill the Delphi 64 bit debugger, while they do not using the 32 bit platform.
Replacing the AssertErrorProc
and just doing some logging there remedies the situation, both for the Delphi debugger as our C# and Delphi hosts.
Both hosts also survive throwing exceptions at the place of the assertions. The exceptions are caught by the except blocks that are in place.
The problem isn't reproducible using Delphi XE3 (thanks @David for helping out on this), so the current status is that this is tied to the (buggy) exception/assertion handling in Delphi XE2, especially on the 64 bit platform.
I have a Delphi DLL which is meant to be called from a self-hosted C# web-service. For debugging purposes this DLL can also be called from a Delphi executable.
The DLL can be and has been used successfully from both the Delphi and C# hosts.
Today I ran into the situation of triggering an assertion in the code executed when the DLL is initialized and found that the assertion is successfully prevented from leaving the DLL when hosted by a Delphi process, but is not caught and causes the host to die when that is a C# process.
The Delphi DLL has its own DllProc procedure which is called manually from the dpr because the Delphi RTL "hijacks" the entry point to allow for unit initialization. See http://docwiki.embarcadero.com/VCL/XE/en/System.DLLProc for details.
Delphi DLL dpr code:
begin
DllProc := MyDllMain;
MyDllMain(DLL_PROCESS_ATTACH);
end.
The custom dll main procedure simply ensures that some structures are initialized when the DLL is first loaded and are finalized when the last "loader" leaves.
procedure MyDllMain(Reason: Integer);
begin
// ...
// DLL_PROCESS_ATTACH:
begin
if _RefCount < 1 then
InitializeDLL;
Inc(_RefCount);
end;
// ...
end;
The InitializeDLL procedure is protected with a try except block, specifically meant to prevent exceptions escaping the DLL.
procedure InitializeDLL;
begin
try
// Some code triggering an Assert(False, 'Whatever');
except
on E: Exception do
TLogger.LogException('InitializeDLL');
// Don't raise through. Exceptions should not leave DLL.
end;
end;
The Delphi host manually calls LoadLibrary for the Delphi DLL, retrieves pointers to the functions it needs and calls the DLL using those.
procedure InternalSetup;
begin
FLibrary := LoadLibrary(CLibraryPath);
GetResource := GetProcAddress(FLibrary, 'GetResource');
PostResource := GetProcAddress(FLibrary, 'PostResource');
PutResource := GetProcAddress(FLibrary, 'PutResource');
DeleteResource := GetProcAddress(FLibrary, 'DeleteResource');
end;
Call: Result := GetResource(INVALID_URI, {aQueryParams=}'', {out}ResponseBody);
The C# host includes the following code to call the DLL
[DllImport("EAConfigurationEngine.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
// Delphi:
// function GetResource(aURI: string; aQueryParams: string; out aResponseBody: WideString): THTTPStatusCode; stdcall; export;
private static extern int GetResource(
string uri,
string queryParams,
[MarshalAs(UnmanagedType.BStr)] out string responseBody);
As stated above the except block is hit when the host for the DLL is a Delphi executable. However, when a call is made to the Delphi DLL from a C# host, the assertion triggers, the except block is not reached (the message isn't logged, instead the uninitialized logger logs an unhandled exception about the assertion), and the C# process dies with the "vshost.exe has stopped working" dialog.
What is causing this to happen and how can I prevent it?
Assertions are a particular flavour of exception that require some additional scaffolding and support from the compiler.
If you take a look at the routines involved (in the SysUtils unit) there are a lot of assumptions described such as for example:
{ This code is based on the following assumptions: }
{ - Our direct caller (AssertErrorHandler) has an EBP frame }
{ - ErrorStack points to where the return address would be if the }
{ user program had called System.@RaiseExcept directly }
This comment is just one of many discussing the assumptions involved with the ASSERT() mechanism.
Whether it is this aspect of the assertions implementation in the Delphi compiler that is involved or not, it seems likely to me that these assumptions are not valid when running in a C# host process. If it is these assumptions that are behind the problem, then raising an exception in the "normal" way may avoid the problem without forcing you to change anything other than the way that you raise the exception itself.
Try replacing your ASSERT(FALSE, 'Whatever') with a simple call to raise an EAssertionFailed exception directly (i.e. not involving the compiler scaffolding and assumptions that ASSERT() invokes).
You can still make the code subject to conditional compilation to achieve the same effect as using the Enable Assertions compiler option (compiler option "C"):
{$ifopt C+} // only if compiling with assertions enabled
raise EAssertionFailed.Create('Whatever');
{$endif}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With