I kept putting this post off… ok, ok… that was a really bad pun…
Seems there was a little bit of concern about my last post regarding the new ‘delayed’ directive. Christian Wimmer had a few critiques regarding the implementation and how the exception that was raised was an EExternalException with an obscure exception code. There is a simple explanation for this. The delay load helper functions were taken directly from the C++Builder RTL. By that I mean, the delayhlp.c file gets built to a .obj file by the C++ compiler and then directly linked into the Delphi System unit. There were several key reasons for this. The first of which was the code was already written, has been in many versions of C++Builder RTL (including back in the old Borland C++ days) and has been extensively tested. Another reason is that in order for Delphi and C++Builder to “play-nice,” when a Delphi unit is linked in C++Builder that contains a reference to a delay load import, ILink32 takes over the duties of generating the proper delay load functionality into the PE file. So the C++RTL version of delayhlp.c is what is used. In order to things to remain consistent, this is the route taken. Had there been two delay-load helper functions in the case of a C++Builder application, then any existing C++ code that used the delay load hooks would only work on other C++ code and vice-versa.
Fear not, all is not lost. To satisfy our good friend, Christian’s request, here is a unit that you can use to generate nice, unique Delphi specific exceptions. This is accomplished by leveraging the ability for the delayhlp.c code to be “hooked.” This code also demonstrates another new Delphi language feature, class constructors and destructors. I will describe them in better detail in a subsequent post. If you never reference any of the declared exception types in this unit, the hook is not installed and the normal EExternalException is raised. Presumably you would use this unit for the purpose of actually catching the exceptions and doing something interesting with them. Another thing to note is that you should also be able to add this unit to a C++Builder application and it work for any delay-load functions done in the existing way for C++ and any Delphi units that reference Delphi imports with the delayed directive.
unit DelayExcept;
interface
uses SysUtils;
type
EDliException = class(Exception)
private
class constructor Create;
class destructor Destroy;
end;
EDliLoadLibraryExeception = class(EDliException)
private
FDllName: string;
public
constructor Create(const ADllName: string); overload;
property DllName: string read FDllName;
end;
EDliGetProcAddressException = class(EDliException)
private
FDllName: string;
FExportName: string;
public
constructor Create(const ADllName, AExportName: string); overload;
constructor Create(const ADllName: string; AOrdinal: LongWord); overload;
property DllName: string read FDllName;
property ExportName: string read FExportName;
end;
implementation
{ EDliLoadLibraryExeception }
constructor EDliLoadLibraryExeception.Create(const ADllName: string);
begin
inherited Create(Format('Unable to load ''%s''', [ADllName]));
FDllName := ADllName;
end;
{ EDliGetProcAddressException }
constructor EDliGetProcAddressException.Create(const ADllName, AExportName: string);
begin
inherited Create(Format('Unable to locate export name ''%s'' in ''%s''', [AExportName, ADllName]));
FDllName := ADllName;
FExportName := AExportName;
end;
constructor EDliGetProcAddressException.Create(const ADllName: string;
AOrdinal: LongWord);
begin
inherited Create(Format('Unable to locate export ordinal ''%d'' in ''%s''', [AOrdinal, ADllName]));
FDllName := ADllName;
FExportName := IntToStr(AOrdinal);
end;
function DelayLoadFailureHook(dliNotify: dliNotification; pdli: PDelayLoadInfo): Pointer; stdcall;
begin
Result := nil;
if dliNotify = dliFailLoadLibrary then
raise EDliLoadLibraryExeception.Create(pdli.szDll);
if dliNotify = dliFailGetProcAddress then
if pdli.dlp.fImportByName then
raise EDliGetProcAddressException.Create(pdli.szDll, pdli.dlp.szProcName)
else
raise EDliGetProcAddressException.Create(pdli.szDll, pdli.dlp.dwOrdinal);
end;
{ EDliException }
class constructor EDliException.Create;
begin
SetDliFailureHook(DelayLoadFailureHook);
end;
class destructor EDliException.Destroy;
begin
SetDliFailureHook(nil);
end;
end.
That's certainly better -- uplevel-specific code could just catch EDliException and *usually* be fine... as long as there isn't an exception in the middle of the process that's from yet a later OS.
ReplyDeleteBut is there really no way to use this feature to check whether an API is available, without actually calling it? Seems like it's not even worth going down the code path of e.g. doing the calculations for a taskbar button's progress bar, if the API functions won't be available.
And btw, why is it that every time I post a comment on your blog, I get a page that just says "Email not matching comment ID"? Happens both at home and at work, in both Chrome and Firefox.
ReplyDeleteThat's a nice code, thank you!
ReplyDelete"and has been extensively tested" -> that must be joke. We have a serious issue with this. We have an application which consists of many dlls. This dlls are loaded and unloaded during the applications lifetime. All of the dlls and the exe are build with runtime packages. Now imagine that some of the dlls are importing functions from win32 with the delayed directive. The delay-loading stuff is registering the ImgDelayDescr from the DLL in its list. The dll is unloaded sometimes later. Now when shutting down the exe it crashs because __FUnloadDelayLoadedDLL(NULL) is called in ShutdownDelayHelp.
ReplyDeleteAny hints?