http://delphi.about.com/od/windowsshellapi/l/aa093003a.htm

Page 1: How Delphi dispatches messages in windowed applications

Article submitted by Catalin Ionescu for the Delphi Programming Quickies Contest - Round #2 Winner!

Learn how to send signals to non-windowed applications by using AllocateHWND and DefWindowProc.

In this article we also briefly describe what Delphi does in the background to intercept Windows messages,

how can we write our own message handler for a windowed application and

how to obtain a unique message identifier that we can safely use in our applications.

We'll also discover and fix a small bug in the Delphi DeallocateHWND procedure along the route.

How Delphi dispatches messages in windowed applications

In the Windows environment a lot of messages flow in the background.

Each time the mouse is moved, a key is pressed, a window is moved or needs to be redrawn and in many other cases

one or more messages is generated and hopefully finds it's way to the appropriate message handler code

that understands and reacts appropriately to that very specific message.

To see some of the messages that are generated automatically by Windows or as a response to your input you can fire up WinSight and peek around.

If we need to receive and react to one of these messages in our application we need to write a message handler.

For example if we need to respond to Windows signaling our main form that it needs to erase the background

(WM_ERASEBKGND message) the typical code that we should place in the interface may be:

  1. procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;

Then in the implementation part we would write the actual code that erases the form background.

This is called a message handler.

Delphi needs to do a lot of work in the background to call the above code for each WM_ERASEBKGND message generated by Windows.

First it needs to intercept all Windows messages targeted at our particular form.

Then it needs to sort them out based on the message identifier (WM_ERASEBKGND in the above example)

and find if we implemented a message handler for any of them.

In our above code we signal Delphi that we're only interested in receiving background erase notifications and not,

for instance, mouse clicks.

So Delphi calls our message handler for all background erase notifications and one of its own message handlers

for all other messages received by the form.

Fortunately Windows has a very convenient way of intercepting all messages passed to a form.

Each form has a window procedure that gets called for each and every message the form receives.

As a new form is created the operating system provides a default window procedure

that can be changed later by the application that owns the form if it needs to react on one or more messages.

This is exactly what Delphi does.

As each new form is created Delphi overrides the default window procedure with its own message interceptor and dispatcher.

It is then a simple matter of comparison to find the appropriate message handler,

either internal or written by us, for each specific message identifier.

Non-windowed?

If we write a non-windowed application, for instance a console application or a non-interactive service,

then Delphi doesn't have a default window procedure to intercept and thus we can't rely on him to call our message handler.

We need to write our own window procedure.

But, as our application doesn't have a window, neither do Windows provide us with a window procedure to intercept.

Isn't there a way to trick Windows in thinking we have a window? Find the answer on the next page...

Page 2: How to send messages to non-windowed applications

Now that you know how Delphi dispatches messages in windowed applications,

it's time to trick Windows in thinking we have a window in a non-windowed application

How to send messages to non-windowed applications

Isn't there a way to trick Windows in thinking we have a window?

We could create one but having tens of windows displayed all across the screen

just for the purpose of intercepting Windows messages is inaesthetical and unpractical.

What if we could create a window but mark it in such way so it is not displayed on the screen?

We could reach our goal of having a window procedure and

at the same time don't fill the screen with dummy empty windows.

Again Delphi VCL addresses this very specific need by providing a convenient wrapper that handles all the low-level API calls.

It provides two functions that must be used in pairs:

AllocateHWND and DeallocateHWND.

First one accepts a single parameter that is our window procedure

and the second one does the cleanup when we no longer need the invisible window.

We have created a sample application that shows the usage of AllocateHWND/DeallocateHWND functions.

To simulate a non-windowed application we will use a TThread descendent, defined as follows:

  1. TTestThread = class(TThread)
  2. private
  3. FSignalShutdown: boolean;
  4. { hidden window handle }
  5. FWinHandle: HWND;
  6. protected
  7. procedure Execute; override;
  8. { our window procedure }
  9. procedure WndProc(var msg: TMessage);
  10. public
  11. constructor Create;
  12. destructor Destroy; override;
  13. procedure PrintMsg;
  14. end;
Creating the hidden window

In the TTestThread constructor we create our hidden window which is destroyed in the TTestThread destructor:

  1. constructor TTestThread.Create;
  2. begin
  3. FSignalShutdown := False;
  4. { create the hidden window, store it's handle and change the default window procedure provided by Windows with our window procedure }
  5. FWinHandle := AllocateHWND(WndProc);
  6. inherited Create(False);
  7. end;
  8.  
  9. destructor TTestThread.Destroy;
  10. begin
  11. { destroy the hidden window and free up memory }
  12. DeallocateHWnd(FWinHandle);
  13. inherited;
  14. end;

To test for the message routing we use a simple boolean (FSignalShutdown)

that we initially set to False in the constructor and

will hopefully be set to True in the window procedure (WndProc) upon receiving our message.

The window procedure

In order to keep things simple we have implemented a bare-bone window procedure as follows:

  1. procedure TTestThread.WndProc(var msg: TMessage);
  2. begin
  3. if Msg.Msg = WM_SHUTDOWN_THREADS then
  4. { if the message id is WM_SHUTDOWN_THREADS do our own processing }
  5. FSignalShutdown := True
  6. else
  7. { for all other messages call the default window procedure }
  8. Msg.Result := DefWindowProc(FWinHandle, Msg.Msg, Msg.wParam, Msg.lParam);
  9. end;

This procedure is called for each and every Windows message,

so we need to filter out only those that are interesting,

in this case WM_SHUTDOWN_THREADS.

For all other messages that are not handled by our application remember to call the default Windows procedure.

Even if this is not so important for our non-visible non-interactive window

it is a good practice and missing this step may yield to unpredictable behaviors.

Message identifier?

If applications were to use arbitrary message id's their operation would soon interfere

and messages meant for one application would be interpreted in unknown and unwanted ways by others.

To prevent this Windows provides a way for each application or related applications to obtain a unique message identifier.

How? Find on the next page...

Page 3: Obtaining a unique message identifier. The DeallocateHWND bug.

Until now, we've covered how Delphi dispatches messages in windowed applications,

and how to send messages to non-windowed applications,

we move on to obtaining a unique message identifier.

Obtaining a unique message identifier

As stated on the previous page, if applications were to use arbitrary message id's their operation would soon interfere

and messages meant for one application would be interpreted in unknown and unwanted ways by others.

To prevent this Windows provides a way for each application or related applications to obtain a unique message identifier.

  1. var
  2. WM_SHUTDOWN_THREADS: Cardinal;
  3.  
  4. procedure TfrmSgnThreads.FormCreate(Sender: TObject);
  5. begin
  6. WM_SHUTDOWN_THREADS := RegisterWindowMessage('TVS_Threads');
  7. end;

We pass a unique string identifier to the RegisterWindowMessage and it returns a message identifier that is guaranteed to be unique across a Windows session.

The application

All that remains is to put all the above code together. You can download the full source code.

To test the application we create a few threads by pressing the New Thread button,

then notice how all of them correctly receive the WM_SHUTDOWN_THREADS message

when we press the Send Signal button.

To view the internal flow of code we printed a message when each thread is created and another message

when the thread receives the message and is destroyed.

Everything works as expected.

But as the threads and the hidden windows are destroyed

we will soon notice a lot of exceptions popping up.

DeallocateHWND bug

We tried to narrow down the source of the problem.

From the exception message we concluded there's probably a reference to unallocated memory at some point.

The only places where we deallocate memory are when the threads themselves are destroyed

and when the hidden window is destroyed.

First we moved the DeallocateHWND call to the Execute procedure and comment the FreeOnTerminate line,

so the threads don't destroy automatically:

  1. procedure TTestThread.Execute;
  2. begin
  3. { FreeOnTerminate := True; }
  4. while NOT FSignalShutdown do
  5. begin
  6. Sleep();
  7. end;
  8. Synchronize(PrintMsg);
  9. { destroy the hidden window and free up memory }
  10. DeallocateHWnd(FWinHandle);
  11. end;

The errors still show up.

So it must be something related to the DeallocateHWND call

so we look at the Delphi DeallocateHWnd implementation which is in the Classes unit:

  1. procedure DeallocateHWnd(Wnd: HWND);
  2. var
  3. Instance: Pointer;
  4. begin
  5. Instance := Pointer(GetWindowLong(Wnd, GWL_WNDPROC));
  6. DestroyWindow(Wnd);
  7. if Instance <> @DefWindowProc then FreeObjectInstance(Instance);
  8. end;

At first glance everything looks fine.

The window is destroyed and the memory occupied by our window procedure is freed.

But... after we free up the memory occupied by our window procedure a few messages

are still routed to the hidden window

(yes, there are still messages routed to that window, including a bunch of WM_DESTROY and its relatives).

So Windows will try to reference an unallocated memory space

when trying to execute our window procedure and thus an exception will pop up.

The solution we have found is to slightly alter the DeallocateHWnd code to change back the window procedure

to the default one provided by Windows before we free up the memory for the code.

  1. procedure TTestThread.DeallocateHWnd(Wnd: HWND);
  2. var
  3. Instance: Pointer;
  4. begin
  5. Instance := Pointer(GetWindowLong(Wnd, GWL_WNDPROC));
  6. if Instance <> @DefWindowProc then
  7. begin
  8. { make sure we restore the default
  9. windows procedure before freeing memory }
  10. SetWindowLong(Wnd, GWL_WNDPROC, Longint(@DefWindowProc));
  11. FreeObjectInstance(Instance);
  12. end;
  13. DestroyWindow(Wnd);
  14. end;

You can download the full source code of the application with the fix here.

We notice that all errors are gone and conclude the error was indeed in the DeallocateHWnd code.

If you have any questions or comments I would like to receive them in the Delphi Programming Forum

and I'll try to answer all to the best of my knowledge and abilities.

Sending messages to non-windowed applications -- AllocateHWnd, DeallocateHWnd的更多相关文章

  1. Socket.io 0.7 – Sending messages to individual clients

    Note that this is just for Socket.io version 0.7, and possibly higher if they don’t change the API a ...

  2. Receive Windows Messages In Your Custom Delphi Class - NonWindowed Control - AllocateHWnd

    http://delphi.about.com/od/windowsshellapi/a/receive-windows-messages-in-custom-delphi-class-nonwind ...

  3. Thread message loop for a thread with a hidden window? Make AllocateHwnd safe

    Thread message loop for a thread with a hidden window? I have a Delphi 6 application that has a thre ...

  4. AllocateHwnd is not Thread-Safe

    http://www.thedelphigeek.com/2007/06/allocatehwnd-is-not-thread-safe.html http://gp.17slon.com/gp/fi ...

  5. Fedora 24中的日志管理

    Introduction Log files are files that contain messages about the system, including the kernel, servi ...

  6. Elixir - Hey, two great tastes that go great together!

    这是Elixir的作者 José Valim 参与的一次技术访谈,很有料,我们可以了解Elixir的一些设计初衷,目标等等. 原文在: http://rubyrogues.com/114-rr-eli ...

  7. windows消息机制详解(转载)

    消息,就是指Windows发出的一个通知,告诉应用程序某个事情发生了.例如,单击鼠标.改变窗口尺寸.按下键盘上的一个键都会使Windows发送一个消息给应用程序.消息本身是作为一个记录传递给应用程序的 ...

  8. actor concurrency

    The hardware we rely on is changing rapidly as ever-faster chips are replaced by ever-increasing num ...

  9. GO语言的开源库

    Indexes and search engines These sites provide indexes and search engines for Go packages: godoc.org ...

随机推荐

  1. WPF为提示信息文本增加闪烁效果

    程序通常需要显示某些提醒用户警示的信息,如:收件箱(40)其中数量闪烁就会起到警示效果.可以适用如下Storyboard实现: <ItemsControl.ItemTemplate> &l ...

  2. 建立连接ALM的xml config文件

    我就不贴所有的了,如果有谁想要所有源码和应用程序,可以密我 这里我贴下如何在第一次运行的时候自动建立一个ALMConfig的xml文件 private static void CreateALMCon ...

  3. jquery、js操作checkbox全选反选

    全选反选checkbox在实际应用中比较常见,本文有个不错的示例,大家可以参考下 操作checkbox,全选反选//全选 function checkAll() { $('input[name=&qu ...

  4. 操作符重载.xml

    pre{ line-height:1; color:#1e1e1e; background-color:#d2d2d2; font-size:16px;}.sysFunc{color:#627cf6; ...

  5. java中子类与基类变量间的赋值

    Java中子类与基类变量间的赋值 子类对象可以直接赋给基类变量. 基类对象要赋给子类对象变量,必须执行类型转换, 其语法是: 子类对象变量=(子类名)基类对象名; 也不能乱转换.如果类型转换失败Jav ...

  6. BITED数学建模七日谈之五:怎样问数学模型问题

    下面进入数学建模经验谈第五天:怎样问数学模型问题 写这一篇的目的主要在于帮助大家能更快地发现问题和解决问题,让自己的模型思路有一个比较好的形成过程. 在我们学习数学模型.准备比赛的时候,经常会遇到各种 ...

  7. 使用Eclipse的几个必须掌握的快捷方式(能力工场小马哥收集)

    “工若善其事,必先利其器”,感谢Eclipse,她 使我们阅读一个大工程的代码更加容易,在阅读的过程中,我发现掌握几个Eclipse的快捷键会使阅读体验更加流畅,写出来与诸君分享,欢迎补充. 1. C ...

  8. Mapreduce执行过程分析(基于Hadoop2.4)——(二)

    4.3 Map类 创建Map类和map函数,map函数是org.apache.hadoop.mapreduce.Mapper类中的定义的,当处理每一个键值对的时候,都要调用一次map方法,用户需要覆写 ...

  9. php和.net 的加密解密

    PHP版: $key = 335ff'; /* * 加密方法 * @param string $input,待加密的字符串 * @param string $key,加密的密码(只能为8位长) * @ ...

  10. windows平台下安装python的setuptools工具

    到下面的网址下载setuptools-0.6c11.win32-py2.7.exe http://pypi.python.org/pypi/setuptools#files 然后安装setuptool ...