Friday, October 26, 2007

Understand HANDLE allocated to Delphi Packages in Windows environment

We may seldom need to deal with Windows API when using Delphi VCL to develop a Win32 application. The most common information we may want to retrieve is the file name of the main executable in runtime. In this case, we use
MyExeFileName := ParamStr(0);
Delphi packages (.bpl) itself are a better windows .DLL. We may use it like normal windows .DLL file but it is more than that. Here are some useful tricks when work with dynamic packages. HINSTANCE HINSTANCE is a cardinal variable available at runtime. It will always return the module handle allocated by windows depends on where the current execution point is. For example, if current code is at the main .exe, the HINSTANCE will return handle of the .exe module. If current code is at .bpl package, the HINSTANCE will return handle of the .bpl package. GetModuleHandle You may use GetModuleHandle to retrieve the handle of known .exe or .bpl file name. For example,
hExe := GetModuleHandle(PAnsiChar('main.exe'))
will return the handle of main.exe module.
hBPL := GetModuleHandle(PAnsiChar('package.bpl'))
will return the handle of package.bpl package. If we pass a NULL parameter to the function,
hNULL := GetModuleHandle(nil)
It will always return the handle of main executable even if current execution point is in a package. Thus, hNULL and hEXE should have same value. You may now understand what is the value returned by LoadPackage and the value required by UnloadPackage. Both values are handle allocated by windows. So,
m := LoadPackage('package.dll');
hBPL := GetModuleHandle(PAnsiChar('package.dll'));
Assert(m = hBPL);  // must be equal
To test if current execution point is at main executable, you may use
if HINSTANCE = GetModuleHandle(nil) then
// in main executable
// in a package or dll module
GetModuleName GetModuleName(hEXE) will return the main executable file name. It should be same as ParamStr(0). GetModuleName(hBPL) will return the package file name. GetModuleName(HINSTANCE) will return the current module name (either Main executable or package) depends on where the current execution point is.

Thursday, October 25, 2007

Make non TWinControl descendant response to windows messages

Although using SendMessage and PostMessage isn't a good practice in OO design. However, I do need it in some situation. All the while I wrote TWinControl descendant to handle the custom messages: W := TWinControl_Descendant.Create(nil); W.Parent := Application.MainForm; PostMessage(W.Handle, 10000, 0, 0); I got to set the Parent for the instance else the handle won't be allocated. It is troublesome if the application I wrote isn't a window form application. I raise a post in codegear newsgroup "borland.public.delphi.vcl.components.using.win32" of title "Is that possible to allocate windows handle to TObject direct descendant" and get some great replies. The TTimer component in Delphi VCL already is a good example of how to make a non TWinControl descendant response to windows messages. I then write a prototype to try out and it work as expected.
  TMyObject = class(TObject)
    FHandle: THandle;
    procedure WndProc(var Message: TMessage);
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    property Handle: THandle read FHandle;

procedure TMyObject.AfterConstruction;
  FHandle := AllocateHWnd(WndProc);

procedure TMyObject.BeforeDestruction;

procedure TMyObject.WndProc(var Message: TMessage);
  if Message.Msg = 10000 then
    ShowMessage('Message received')
    Message.Result := DefWindowProc(FHandle, Message.Msg, Message.wParam, Message.lParam);

var C: TMyObject;
  C := TMyObject.Create;
    SendMessage(C.Handle, 10000, 0, 0);

Wednesday, October 24, 2007

Improve the loading speed of Delphi application built with runtime package

I just come across with the article The ultimate Delphi IDE start-up hack When we use SysUtils.LoadPackage in Delphi, the following procedure will be invoked:
procedure InitializePackage(Module: HMODULE; AValidatePackage: TValidatePackageProc);
 TPackageLoad = procedure;
 PackageLoad: TPackageLoad;
 CheckForDuplicateUnits(Module, AValidatePackage);
 @PackageLoad := GetProcAddress(Module, 'Initialize'); //Do not localize
 if Assigned(PackageLoad) then
   raise EPackageError.CreateFmt(sInvalidPackageFile, [GetModuleName(Module)]);
If we are very sure that our .bpl packages has no duplicate unit name, we may safely ignore the call to "CheckForDuplicateUnits" procedure. It should improve the loading speed of your Delphi application that built with runtime packages.