加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

delphi – 如何使AllocateHwnd线程安全?

发布时间:2020-12-15 10:05:24 所属栏目:大数据 来源:网络整理
导读:VCL组件设计为仅从应用程序的主线程使用.对于视觉部件,这不会给我带来任何困难.然而,我有时希望能够使用例如TTMI的非可视化组件从后台线程.或者确实只是创建一个隐藏的窗口.这是不安全的,因为依赖于AllocateHwnd.现在,AllocateHwnd不是线程安全的,我所理解的
VCL组件设计为仅从应用程序的主线程使用.对于视觉部件,这不会给我带来任何困难.然而,我有时希望能够使用例如TTMI的非可视化组件从后台线程.或者确实只是创建一个隐藏的窗口.这是不安全的,因为依赖于AllocateHwnd.现在,AllocateHwnd不是线程安全的,我所理解的是设计.

有没有一个容易的解决方案,让我从后台线程使用AllocateHwnd?

解决方法

这个问题可以像这样解决:

>获取或实现一个线程安全版本的AllocateHwnd和DeallocateHwnd.
>替换VCL不安全版本的这些功能.

对于项目1,我使用Primo? Gabrijelcic’s代码,如他在blog article上所述的主题.对于项目2,我简单地使用非常有名的技巧在运行时修补代码,并用无条件的JMP指令来替代不安全例程的开始,将指令重定向到线程安全功能.

将它们放在一起产生以下单元.

(* Makes AllocateHwnd safe to call from threads. For example this makes TTimer
   safe to use from threads.  Include this unit as early as possible in your
   .dpr file.  It must come after any memory manager,but it must be included
   immediately after that before any included unit has an opportunity to call
   Classes.AllocateHwnd. *)
unit MakeAllocateHwndThreadsafe;

interface

implementation

{$IF CompilerVersion >= 23}{$DEFINE ScopedUnitNames}{$IFEND}
uses
  {$IFDEF ScopedUnitNames}System.SysUtils{$ELSE}SysUtils{$ENDIF},{$IFDEF ScopedUnitNames}System.Classes{$ELSE}Classes{$ENDIF},{$IFDEF ScopedUnitNames}Winapi.Windows{$ELSE}Windows{$ENDIF},{$IFDEF ScopedUnitNames}Winapi.Messages{$ELSE}Messages{$ENDIF};

const //DSiAllocateHwnd window extra data offsets
  GWL_METHODCODE = SizeOf(pointer) * 0;
  GWL_METHODDATA = SizeOf(pointer) * 1;

  //DSiAllocateHwnd hidden window (and window class) name
  CDSiHiddenWindowName = 'DSiUtilWindow';

var
  //DSiAllocateHwnd lock
  GDSiWndHandlerCritSect: TRTLCriticalSection;
  //Count of registered windows in this instance
  GDSiWndHandlerCount: integer;

//Class message dispatcher for the DSiUtilWindow class. Fetches instance's WndProc from
//the window extra data and calls it.
function DSiClassWndProc(Window: HWND; Message,WParam,LParam: longint): longint; stdcall;
var
  instanceWndProc: TMethod;
  msg            : TMessage;
begin
  {$IFDEF CPUX64}
  instanceWndProc.Code := pointer(GetWindowLongPtr(Window,GWL_METHODCODE));
  instanceWndProc.Data := pointer(GetWindowLongPtr(Window,GWL_METHODDATA));
  {$ELSE}
  instanceWndProc.Code := pointer(GetWindowLong(Window,GWL_METHODCODE));
  instanceWndProc.Data := pointer(GetWindowLong(Window,GWL_METHODDATA));
  {$ENDIF ~CPUX64}
  if Assigned(TWndMethod(instanceWndProc)) then
  begin
    msg.msg := Message;
    msg.wParam := WParam;
    msg.lParam := LParam;
    msg.Result := 0;
    TWndMethod(instanceWndProc)(msg);
    Result := msg.Result
  end
  else
    Result := DefWindowProc(Window,Message,LParam);
end; { DSiClassWndProc }

//Thread-safe AllocateHwnd.
//  @author  gabr [based on http://fidoforum.ru/pages/new46s35o217746.ru.delphi and
//                 TIcsWndHandler.AllocateHWnd from ICS v6 (http://www.overbyte.be)]
//  @since   2007-05-30
function DSiAllocateHWnd(wndProcMethod: TWndMethod): HWND;
var
  alreadyRegistered: boolean;
  tempClass        : TWndClass;
  utilWindowClass  : TWndClass;
begin
  Result := 0;
  FillChar(utilWindowClass,SizeOf(utilWindowClass),0);
  EnterCriticalSection(GDSiWndHandlerCritSect);
  try
    alreadyRegistered := GetClassInfo(HInstance,CDSiHiddenWindowName,tempClass);
    if (not alreadyRegistered) or (tempClass.lpfnWndProc <> @DSiClassWndProc) then begin
      if alreadyRegistered then
        {$IFDEF ScopedUnitNames}Winapi.{$ENDIF}Windows.UnregisterClass(CDSiHiddenWindowName,HInstance);
      utilWindowClass.lpszClassName := CDSiHiddenWindowName;
      utilWindowClass.hInstance := HInstance;
      utilWindowClass.lpfnWndProc := @DSiClassWndProc;
      utilWindowClass.cbWndExtra := SizeOf(TMethod);
      if {$IFDEF ScopedUnitNames}Winapi.{$ENDIF}Windows.RegisterClass(utilWindowClass) = 0 then
        raise Exception.CreateFmt('Unable to register DSiWin32 hidden window class. %s',[SysErrorMessage(GetLastError)]);
    end;
    Result := CreateWindowEx(WS_EX_TOOLWINDOW,'',WS_POPUP,HInstance,nil);
    if Result = 0 then
      raise Exception.CreateFmt('Unable to create DSiWin32 hidden window. %s',[SysErrorMessage(GetLastError)]);
    {$IFDEF CPUX64}
    SetWindowLongPtr(Result,GWL_METHODDATA,NativeInt(TMethod(wndProcMethod).Data));
    SetWindowLongPtr(Result,GWL_METHODCODE,NativeInt(TMethod(wndProcMethod).Code));
    {$ELSE}
    SetWindowLong(Result,cardinal(TMethod(wndProcMethod).Data));
    SetWindowLong(Result,cardinal(TMethod(wndProcMethod).Code));
    {$ENDIF ~CPUX64}
    Inc(GDSiWndHandlerCount);
  finally LeaveCriticalSection(GDSiWndHandlerCritSect); end;
end; { DSiAllocateHWnd }

//Thread-safe DeallocateHwnd.
//  @author  gabr [based on http://fidoforum.ru/pages/new46s35o217746.ru.delphi and
//                 TIcsWndHandler.AllocateHWnd from ICS v6 (http://www.overbyte.be)]
//  @since   2007-05-30
procedure DSiDeallocateHWnd(wnd: HWND);
begin
  if wnd = 0 then
    Exit;
  DestroyWindow(wnd);
  EnterCriticalSection(GDSiWndHandlerCritSect);
  try
    Dec(GDSiWndHandlerCount);
    if GDSiWndHandlerCount <= 0 then
      {$IFDEF ScopedUnitNames}Winapi.{$ENDIF}Windows.UnregisterClass(CDSiHiddenWindowName,HInstance);
  finally LeaveCriticalSection(GDSiWndHandlerCritSect); end;
end; { DSiDeallocateHWnd }

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address,Size,PAGE_EXECUTE_READWRITE,OldProtect) then begin
    Move(NewCode,Address^,Size);
    FlushInstructionCache(GetCurrentProcess,Address,Size);
    VirtualProtect(Address,OldProtect,@OldProtect);
  end;
end;

type
  PInstruction = ^TInstruction;
  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

procedure RedirectProcedure(OldAddress,NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;//jump relative
  NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
  PatchCode(OldAddress,NewCode,SizeOf(NewCode));
end;

initialization
  InitializeCriticalSection(GDSiWndHandlerCritSect);
  RedirectProcedure(@AllocateHWnd,@DSiAllocateHWnd);
  RedirectProcedure(@DeallocateHWnd,@DSiDeallocateHWnd);

finalization
  DeleteCriticalSection(GDSiWndHandlerCritSect);

end.

在.dpr文件的单位列表中必须包含该单元.显然,它不会出现在任何自定义内存管理器之前,但它应该立即出现.原因是在对AllocateHwnd进行任何调用之前必须先安装替换例程.

更新我已经合并了最新版本的Primo?的代码,他寄给我.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读