How can I make AllocateHwnd threadsafe?

后端 未结 3 1207
情话喂你
情话喂你 2020-12-01 05:59

VCL components are designed to be used solely from the main thread of an application. For visual components this never presents me with any difficulties. However, I would so

相关标签:
3条回答
  • 2020-12-01 06:15

    Since you have already written code that operates in a dedicated thread, I would assume you don't expect any code to run while this code waits for something. In that case you could just call Sleep either with a specific number of milliseconds, or with a small amount of milliseconds and use this in a loop to check Now or GetTickCount to see if a certain time has elapsed. Using Sleep will also keep CPU-usage down, since the operating system is signaled that you don't require the thread to keep running for that time.

    0 讨论(0)
  • 2020-12-01 06:25

    Don't use TTimer in a thread, it will never be safe. Have the thread either:

    1) use SetTimer() with a manual message loop. You don't need an HWND if you use a callback function, but you do still have to dispatch messages.

    2) use CreateWaitableTimer() and then call WaitForSingleObject() in a loop until the timer is signalled.

    3) use timeSetEvent(), which is a multi-threaded timer. Just be careful because its callback is called in its own thread so make sure your callback function is thread-safe, and there are restrictions to what you are allowed to call inside that thread. Best to have it set a signal that your real thread waits on an then does its work outside of the timer.

    0 讨论(0)
  • 2020-12-01 06:27

    This problem can be solved like so:

    1. Obtain or implement a threadsafe version of AllocateHwnd and DeallocateHwnd.
    2. Replace the VCL's unsafe versions of these functions.

    For item 1 I use Primož Gabrijelcic's code, as described on his blog article on the subject. For item 2 I simply use the very well-known trick of patching the code at runtime and replacing the beginning of the unsafe routines with unconditional JMP instructions that redirect execution to the threadsafe functions.

    Putting it all together results in the following unit.

    (* 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, WParam,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, CDSiHiddenWindowName, '', WS_POPUP,
          0, 0, 0, 0, 0, 0, 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, GWL_METHODDATA, cardinal(TMethod(wndProcMethod).Data));
        SetWindowLong(Result, GWL_METHODCODE, 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, Size, 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.
    

    This unit must be included very early in the .dpr file's list of units. Clearly it cannot appear before any custom memory manager, but it should appear immediately after that. The reason being that the replacement routines must be installed before any calls to AllocateHwnd are made.

    Update I have merged in the very latest version of Primož's code which he kindly sent to me.

    0 讨论(0)
提交回复
热议问题