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

http://gp.17slon.com/gp/files/dsiwin32.zip

AllocateHwnd is not Thread-Safe

[This article also serves as announcement of DSiWin32 1.26.]

[Update: Reported as QC #47559. Vote for it!]

You're probably asking yourself - what's that AllocateHwnd anyway?

And why must it be thread-safe?

As the Google is guick to tell (BTW, Steve, thanks for the search filter!),

AllocateHwnd is used to create a hidden window which you can use to receive messages in non-windowed components.

Of course, you can use it outside of any component to set up simple and easy messaging subsystem anywhere in your application.

If you need more communication channels, just call AllocateHwnd many times.

I won't bother you with the usage pattern - if you want to use AllocateHwnd and don't know how, use the search link above.

You'll find many examples, including this one from DelphiDabbler, which Steve's searcher lists on the first place.

An example of a very popular component using AllocateHwnd internally is Delphi's TTimer.

That should answer the first question, but what about thread-safety?

Well, many programmers use AllocateHwnd in threaded code to create hidden windows where messages are processed.

Many are also using TTimer inside threads without knowing the first thing about AllocateHwnd.

But almost nobody knows that this is totally unsafe and may lead to rare and obscure crashes.

AllocateHwnd was written with single-threaded VCL applications in mind and you can use it from a thread only if you take special precaution.

Why is AllocateHwnd dangerous

Let's see how the AllocateHwnd is implemented. Following code was copied from D2007's Classes.pas

(in very old Delphis, AllocateHwnd was implemented in Forms.pas):

var
UtilWindowClass: TWndClass = (
style: ;
lpfnWndProc: @DefWindowProc;
cbClsExtra: ;
cbWndExtra: ;
hInstance: ;
hIcon: ;
hCursor: ;
hbrBackground: ;
lpszMenuName: nil;
lpszClassName: 'TPUtilWindow'); function AllocateHWnd(Method: TWndMethod): HWND;
var
TempClass: TWndClass;
ClassRegistered: Boolean;
begin
UtilWindowClass.hInstance := HInstance;
{$IFDEF PIC}
UtilWindowClass.lpfnWndProc := @DefWindowProc;
{$ENDIF}
ClassRegistered := GetClassInfo(HInstance, UtilWindowClass.lpszClassName,
TempClass);
if not ClassRegistered or (TempClass.lpfnWndProc <> @DefWindowProc) then
begin
if ClassRegistered then
Windows.UnregisterClass(UtilWindowClass.lpszClassName, HInstance);
Windows.RegisterClass(UtilWindowClass);
end;
Result := CreateWindowEx(WS_EX_TOOLWINDOW, UtilWindowClass.lpszClassName,
'', WS_POPUP {+ 0}, , , , , , , HInstance, nil);
if Assigned(Method) then
SetWindowLong(Result, GWL_WNDPROC, Longint(MakeObjectInstance(Method)));
end;

Basically, the code registers window class if necessary, creates a new window of that class,

and sets window procedur for that window to MakeObjectInstance(Method).

Nothing special, except this last step. Can you tell why it is necessary at all?

The reason lies in the discrepancy between Delphi's object model and Win32 API, which is not object oriented.

The TWndMethod parameter passed to the AllocateHwnd is not just an address of code,

but contains also the address of the object this method belongs to.

On the other hand, Win32 API wants to call a simple method anytime it has to deliver a message to a window.

MakeObjectInstance bridges this gap.

It manages a linked list of methods together with a dynamically generated code preamble

(address of which is returned from the MakeObjectInstance function).

When Windows calls this code preamble, it makes sure that correct method is called on the correct object.

MakeObjectInstance is complicated, but it works.

That is, until you call it from two threads at the same time.

You see, MakeObjectInstance does nothing to lock its internal list while it is being manipulated.

If you do this from two threads running on two CPUs, or even if you have only one CPU and context switch occurs at a bad time,

internal instance list can get corrupted.

Later, this may lead to crashes, bad program behaviour, you name it. And you'll never find the true culprit.

Admittedly, there is only a small window - few instructions - which are problematic.

In most applications such problems will never occur.

But if you're running 24/7 server which calls AllocateHwnd/DeallocateHwnd constantly from multiple threads,

you can be sure that sooner or later it will crash.

Solution

There are two possible solutions to the problem

- one is to wrap all AllocateHwnd and DeallocateHwnd in some sort of critical section,

spinlock or mutex that will allow only one instance to be called at the same time

and other is to write a better and thread-safe AllocateHwnd.

First solution is somewhat clumsy to implement in production code while the second can be hard to write.

Actually, I search the net wide and deep and found only two alternative AllocateHwnd implementations (references below).

I'm sure there are more. I just couldn't find them.

None of them was really suitable for my needs so I created a third one using ideas from both of them.

My version — DSiAllocateHwnd, DSiDeallocateHwnd and TDSiTimer — has been published as a part of the DSiWin32 library.

This is the current version of my AllocateHwnd alternative:

const
GWL_METHODCODE = SizeOf(pointer) * ;
GWL_METHODDATA = SizeOf(pointer) * ; CDSiHiddenWindowName = 'DSiUtilWindow'; var
GDSiWndHandlerCritSect: TRTLCriticalSection;
GDSiWndHandlerCount: integer; function DSiClassWndProc(Window: HWND; Message, WParam, LParam: longint): longint; stdcall;
var
instanceWndProc: TMethod;
msg : TMessage;
begin
instanceWndProc.Code := Pointer(GetWindowLong(Window, GWL_METHODCODE));
instanceWndProc.Data := Pointer(GetWindowLong(Window, GWL_METHODDATA));
if Assigned(TWndMethod(instanceWndProc)) then
begin
msg.msg := Message;
msg.wParam := WParam;
msg.lParam := LParam;
TWndMethod(instanceWndProc)(msg);
Result := msg.Result
end
else
Result := DefWindowProc(Window, Message, WParam,LParam);
end; { DSiClassWndProc } function DSiAllocateHWnd(wndProcMethod: TWndMethod): HWND;
var
alreadyRegistered: boolean;
tempClass : TWndClass;
utilWindowClass : TWndClass;
begin
Result := ;
FillChar(utilWindowClass, SizeOf(utilWindowClass), );
EnterCriticalSection(GDSiWndHandlerCritSect);
try
alreadyRegistered := GetClassInfo(HInstance, CDSiHiddenWindowName, tempClass);
if (not alreadyRegistered) or (tempClass.lpfnWndProc <> @DSiClassWndProc) then begin
if alreadyRegistered then
Windows.UnregisterClass(CDSiHiddenWindowName, HInstance);
utilWindowClass.lpszClassName := CDSiHiddenWindowName;
utilWindowClass.hInstance := HInstance;
utilWindowClass.lpfnWndProc := @DSiClassWndProc;
utilWindowClass.cbWndExtra := SizeOf(TMethod);
if Windows.RegisterClass(utilWindowClass) = then
raise Exception.CreateFmt('Unable to register DSiWin32 hidden window class. %s',
[SysErrorMessage(GetLastError)]);
end;
Result := CreateWindowEx(WS_EX_TOOLWINDOW, CDSiHiddenWindowName, '', WS_POPUP,
, , , , , , HInstance, nil);
if Result = then
raise Exception.CreateFmt('Unable to create DSiWin32 hidden window. %s',
[SysErrorMessage(GetLastError)]);
SetWindowLong(Result, GWL_METHODDATA, Longint(TMethod(wndProcMethod).Data));
SetWindowLong(Result, GWL_METHODCODE, Longint(TMethod(wndProcMethod).Code));
Inc(GDSiWndHandlerCount);
finally LeaveCriticalSection(GDSiWndHandlerCritSect); end;
end; { DSiAllocateHWnd } procedure DSiDeallocateHWnd(wnd: HWND);
begin
DestroyWindow(wnd);
EnterCriticalSection(GDSiWndHandlerCritSect);
try
Dec(GDSiWndHandlerCount);
if GDSiWndHandlerCount <= then
Windows.UnregisterClass(CDSiHiddenWindowName, HInstance);
finally LeaveCriticalSection(GDSiWndHandlerCritSect); end;
end; { DSiDeallocateHWnd }

There are many differences between this code and Delphi version.

  • My code uses custom DefWindowProc method - DSiClassWndProc.
  • It reserves four extra bytes in each window of the DSiUtilWindow class (utilWindowClass.cbWndExtra setting).
  • It writes both parts of TMethod (code and data) directly into those four bytes of the hidden window's user data.
  • DSiClassWndProc retrieves those four bytes, reconstructs TMethod and calls it directly.
  • When all hidden windows are closed, window class gets unregistered (in DSiDeallocateHwnd).

I admit that this approach to message dispatching is slower than the Delphi's version,

but usually that is not a problem - custom windows are usually created to process some small subset of messages only.

Acknowledgments

The AllocateHwnd problem is not something I have found by myself.

It has been documented for years, but is not well known.

I'd like to thank to:

  • Arno Garrels on the ICS mailing list, who described the problem to me.
  • Francois Piette for providing ICS source code with custom AllocateHwnd solution. My approach is partially based on the Francois' code.
  • Alexander Grischenko, who wrote this solution from which I stole the idea of storing TMethod directly in window's extra data.

AllocateHwnd is not Thread-Safe的更多相关文章

  1. 【转】php Thread Safe(线程安全)和None Thread Safe(NTS,非 线程安全)之分

    Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍. ...

  2. PHP版本VC6与VC9、Thread Safe与None-Thread Safe等的区别

    PHP版本VC6与VC9.Thread Safe与None-Thread Safe等的区别 [摘要]PHP 是一种 HTML 内嵌式的语言,是一种在服务器端执行的嵌入HTML文档的脚本语言,在PHP发 ...

  3. Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分

    Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍. ...

  4. PHP版本VC6和VC9、Non Thread Safe和Thread Safe的区别

    链接:http://www.cnblogs.com/neve/articles/1863853.html 想更新个PHP的版本,PHP的windows版本已经分离出来了,见http://windows ...

  5. PHP5.3中关于VC9和VC6以及Thread Safe和Non Thread Safe版本选择的问题

    转自:http://www.htmer.com/article/716.htm 最近在PHP官网上看到又有新版的PHP下载了,于是上去找找For Windows的版本,可是一看确傻眼了,一共给了四个版 ...

  6. PHP的(Thread Safe与Non Thread Safe)

    在安装xdebug到时候你会有有TS和NTS版本的选择,在以前还有VC6和VC9的版本.如果你没有根据你目前的服务器的状况选择对应的版本的话,那么xdebug是安装不成功的. 一.如何选择 php5. ...

  7. 转:PHP的(Thread Safe与Non Thread Safe)

    在安装xdebug到时候你会有有TS和NTS版本的选择,在以前还有VC6和VC9的版本.如果你没有根据你目前的服务器的状况选择对应的版本的话,那么xdebug是安装不成功的. 一.如何选择 php5. ...

  8. Windows下PHP(Thread Safe与Non Thread Safe)版本说明

    转载“http://www.taoz11.com/archives/300.html” linux下直接下载源码,在服务器上编译即可,发现windows下有4个版本: VC9 x86 Non Thre ...

  9. PHP版本VC6与VC9/VC11/VC14、Thread Safe与None-Thread Safe等的区别

    最近正好在弄一个PHP的程序,在这之前一直没有怎么以接触,发现对PHP版本知识了解不是很清楚,自己看了不少类似的文章,还是感觉不够明确和全面, 网上的结论又都是模棱两可,在此,给出最完整甚至武断的解释 ...

  10. windows zend_guard+apache no ssl+php no Thread Safe fastcgi模式 环境配置

    最近公司要做代码加密,就采用ZEND GUARD 方式加密代码 并进行显示 此文为总结,以备自己以后查看和给需要的同学们参考 采用的php为5.3版本  由于现在加密的更改, 能支持zend guar ...

随机推荐

  1. 【转】 iOS开发之打包上传到App Store——(一)各种证书的理解

    OK,有日子没写iOS开发的相关文章啦,主要是最近的精力都没在这上面,不过既然产品已经快要出来了,就有必要了解一下各种证书啥的(众所周知iOS的一堆证书可是很让人头大呀),最近确实被这个搞得头大,然后 ...

  2. Nginx实现多个站点使用一个端口(配置Nginx的虚拟主机)

    Nginx 是一个轻量级高性能的 Web 服务器, 并发处理能力强, 消耗资源小, 无论是静态服务器还是网站, Nginx 表现更加出色, 作为 Apache 的补充和替代使用率越来越高,目前很多大型 ...

  3. 关于TCP/UDP缓存

    1.修订单个socket的缓冲区大小:通过setsockopt使用SO_RCVBUF来设置接收缓冲区,该参数在设置的时候不会与rmem_max进行对比校验,但是如果设置的大小超过rmem_max的话, ...

  4. RandomAccessFile、FileChannel、MappedByteBuffer读写文件

    s package com.nio; import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IO ...

  5. 用Delphi实现文件关联

      文件关联为我们带来很多的方便.Delphi自带有注册表对象TRegistry,可以通过它取得或改变注册表相关键值的内容. Function GetAssociatedExec(FileExt: S ...

  6. yii框架AR详解

    虽 然Yii DAO可以处理事实上任何数据库相关的任务,但很可能我们会花费90%的时间用来编写一些通用的SQL语句来执行CRUD操作(创建,读取,更新和删除). 同时我们也很难维护这些PHP和SQL语 ...

  7. hihocoder 1356 分隔相同整数 简单贪心

    分析:考虑贪心,考虑填ans[i],前i-1个合法,现在剩下一些数, 那么挑出出现次数最多的数,次数为mx,当前剩余总数为sum 如果sum-mx>=mx-1那么肯定有解,这个想想就知道了(这种 ...

  8. Hive QL

    转自http://www.alidata.org/archives/581 Hive 的官方文档中对查询语言有了很详细的描述,请参考:http://wiki.apache.org/hadoop/Hiv ...

  9. Myeclipse2014 自带的报表功能 与 Eclipse BIRT

    Myeclipse2014 自带的报表功能跟 Eclipse BIRT 差不多,但不兼容   1.只能是MyEclipse Web projects 或者 Report Web project不支持B ...

  10. 自己封装的Socket组件,实现服务端多进程共享Socket对象,协同处理客户端请求

    DotNet.Net.MySocket是SLB.NET(Server Load Balance服务器负载均衡)项目中的核心组件. 在实际的项目中发现,单进程的服务端处理高并发的客户请求能力有限. 所以 ...