MsgHandler Stuff 

www.madshi.net

There are several situations where you need to create an unvisible window for communicating or notifying purposes. If you e.g. want to handle display mode changes in it's own unit, you need to create a window that then can handle the WM_DEVICECHANGE messages. Or if you're writing a multi threaded application, the threads need to communicate, which is often solved by using unvisible windows.

The usual solution is to use the undocumented function "AllocateHWnd", which is implemented in "Forms.pas" and used in several VCL components. But this function has some disadvantages: You have to enter a method, so you can't use a normal procedure as the window procedure. Another (even more important) disadvantage is that you have to allocate one window for each purpose. In winNt that's no big problem (apart from that window enumerations get long). But in win9x window handles are limited to 16k. So you're consuming precious system resources.

The "MsgHandler" stuff described here is similar to "AllocateHWnd" regarding the fact that it is also based on unvisible windows internally, but there are a lot of differences, especially in the usage area. E.g. when using "AllocateHWnd" you need to enter a full self written method, which handles all messages. What this method does exactly is up to you. That means you have the full power, but that also means that you have to do a lot, even if you just need one message to be handled.

In contrast to that, all you need to do when using the "MsgHandler" stuff is adding and deleting message handlers as you like. A message handler is a procedure or a method (both is possible) which gets called when our message handler window receives a specified message. Here are the message handler definitions:

type
  TMsgHandler   = procedure (window, msg: cardinal; wParam, lParam: integer; var result: integer);
  TMsgHandlerOO = procedure (window, msg: cardinal; wParam, lParam: integer; var result: integer) of object;

When adding a message handler, you can choose whether you want to use a specific message value, or whether you just want to use a free one. If you want to have the power, you can force e.g. the message "WM_USER + 777" to be used. If you don't need it that strict, you can simply enter a "0" as the desired message. In this case a free message is used and returned.

Deleting message handlers is as easy. Simply enter the message handler you want to delete together with the message you chose (or that was returned). If you don't enter which message was used, then all message handlers, that were registered with the specified "handler" procedure/method, are deleted.

function AddMsgHandler (handler: TMsgHandler;   msg: cardinal = 0; threadID: cardinal = 0) : cardinal; overload;
function AddMsgHandler (handler: TMsgHandlerOO; msg: cardinal = 0; threadID: cardinal = 0) : cardinal; overload;

function DelMsgHandler (handler: TMsgHandler;   msg: cardinal = 0; threadID: cardinal = 0) : boolean;  overload;
function DelMsgHandler (handler: TMsgHandlerOO; msg: cardinal = 0; threadID: cardinal = 0) : boolean;  overload;

Perhaps you're wondering what happended with the message handler window(s). I've mentioned them, but I've not described them yet. The reason is that you normally don't need to care about them. In the moment when you add the first message handler, a message handler window for the current thread is created. There is always only one message handler window per thread. That saves resources and that furthermore makes it possible to ask for the one message handler window of another thread. When you delete all message handlers in one thread, the message handler window of that thread is freed automatically.

The function "MsgHandlerWindow" returns the message handler window of the specified thread (you can enter a "0" for the current thread). If no such window exists yet and if you're asking for the current thread's window, the message handler window is created. If you're asking for another thread's message handler window which does not exist yet, the result is "0".

function MsgHandlerWindow (threadID: cardinal = 0) : cardinal;

Now let me write a little demo program, which does nothing but wait for the next display mode change and then stops. First I'll write this program with using "AllocateHWnd":

program WaitUntilDisplayModeChanged;

uses Forms, Windows, Messages;

procedure MessageLoop; [...]

var window : dword;

type
  TDummyClass = class
    procedure WndMethod (var Message: TMessage);
  end;

procedure TDummyClass.WndMethod(var Message: TMessage);
begin
  if Message.Msg = WM_DISPLAYCHANGE then
    ExitProcess(0);
  Message.result := DefWindowProc(window, Message.Msg, Message.wParam, Message.lParam);
end;

begin
  window := AllocateHWnd(TDummyClass.Create.WndMethod);
  MessageLoop;
end.

And here comes the version of the same program with the "MsgHandler" stuff. It's much nicer and shorter - and it doesn't need "Forms.pas", which results in a *much* smaller executable:

program WaitUntilDisplayModeChanged;

uses Windows, Messages, madTools;

procedure MessageLoop; [...]

procedure DisplayModeChanged(window, msg: cardinal; lParam, wParam: integer; var result: integer);
begin
  ExitProcess(0);
end;

begin
  AddMsgHandler(DisplayModeChanged, WM_DISPLAYCHANGE);
  MessageLoop;
end.