Has anybody tried SIP Delphi component? I bought it some time ago at a reasonable price with sources in order to replace the old code written for Dialogic HMP. It looks like mail support was not implied, docs and help were absent either though with available codes I was not going to have troubles. And they didn't appear until now when I got stuck with the problem I cannot find a solution to.
The library during the call sends small RTP data packets via UDP every 20 ms and to keep these intervals equal it uses a winsdk function timeSetEvent. Here is the extracts from the code (I simplified it to make the things clearer):
Interface
type
// RTP packet header
TRTPHeader = packed record
Byte80: Byte;
PayloadType: Byte;
SeqNo: WORD;
TimeStamp: DWORD;
SSRC: DWORD;
end;
//RTP packet structure
TRTP = packed record
H: TRTPHeader;
Payload: packed array [0 .. 1023] of Byte;
end;
//class realisation of ISipCall interface
TCall = class(TInterfacedObject, ISipCall)
FRtpPacketToSend:TRTP;//RTP packet
//callback function, it is invoked by TMicrophoneThread regularly
procedure OnMicrophone(const Buffer: Pointer);
end;
//Thread class for timing purposes
TMicrophoneThread = class(TThread)
public
FCall: TCall;//call associated with this thread
FEvent: THandle;// Event handle
FTimerHandle: THandle;// Timer handle
procedure Execute; override;
constructor Create(const ACall: TCall);
destructor Destroy; override;
end;
implementation
procedure TCall.OnMicrophone(const Buffer: Pointer); //callback function, it is invoked by TMicrophoneThread regularly
var socket: TSocket;
begin
//preparing FRtpPacketToSend data, initializing socket, Remote server address
//win32 function, sends data to the “Remote” server
sendto(socket, FRtpPacketToSend, sizeof(FRtpPacketToSend), 0, #Remote, SizeOf(Remote));
end;
//callback function invoked by windows timer every 20 ms
procedure Timer20ms(uTimerID, uMessage: UINT; dwUser, dw1, dw2: DWORD_PTR); stdcall;
begin
SetEvent(TMicrophoneThread(dwUser).FEvent);//Sets the TMicrophoneThread event
end;
constructor TMicrophoneThread.Create(ACall: TCall);
begin
inherited;
FCall:=ACall;
FEvent := CreateEvent(nil, False, False, nil);
//Setting timer
FTimerHandle := timeSetEvent(20, 0, #Timer20ms, Cardinal(Self), TIME_CALLBACK_FUNCTION + TIME_PERIODIC);
end;
destructor TMicrophoneThread.Destroy;
begin
timeKillEvent(FTimerHandle);//removing timer
CloseHandle(FEvent);
inherited;
end;
procedure TMicrophoneThread.Execute;
var
buf: array [0 .. 159] of SmallInt;//buffer data, looks like for storing data between function calls
begin
FillChar(buf, SizeOf(buf), 0);
Repeat
//waiting for the timer to set FEvent from Timer20ms function
if (WaitForSingleObject(FEvent, INFINITE) <> WAIT_TIMEOUT) and not Terminated then
begin
if not Terminated then
try
FCall.OnMicrophone(#buf);
except
end;
end;
until Terminated;
end;
//Using these classes:
// Sip call object
Call:=TCall.Create;
// TMicrophoneThread object creates timer and every 20 ms invokes OnMicrophone function to send UDP data in realtime
Mth= TMicrophoneThread.Create(Call);
This code works fine, voice data flows smoothly. But to my surprise it works perfectly up until the number of simultaneous calls exceeds 16, the 17th and other calls do not receive timer signals. I found that this function is already marked as obsolete and some people encountered the same undocumented restrictions of this function - no more than 16 threads.
Instead of timeSetEvent I tried using CreateTimerQueue/CreateTimerQueueTimer with different parameters:
implementation
var
TimerQueue: THandle;
....
procedure WaitOrTimerCallback(lpParameter: Pointer; TimerOrWaitFired: BOOL); stdcall;
begin
SetEvent(TMicrophoneThread(lpParameter).FEvent);
end;
constructor TMicrophoneThread.Create(ACall: TCall);
begin
inherited;
FCall:=ACall;
FEvent := CreateEvent(nil, False, False, nil);
//Setting timer
CreateTimerQueueTimer(FTimerHandle, TimerQueue, #WaitOrTimerCallback, Self, 0, 20, 0);
end;
...
initialization
TimerQueue := CreateTimerQueue;
Also I tried Sleep and its more advanced realization based on QueryPerformanceFrequency/QueryPerformanceCounter:
procedure TMicrophoneThread.Execute;
var
buf: array [0 .. 159] of SmallInt;
waittime: integer;
begin
FillChar(buf, SizeOf(buf), 0);
repeat
if not Terminated then
try
FCall.OnMicrophone(#buf);
waittime:=round((Now - FCall.GetStartTime)*MSecsPerDay)
if waittime<20 then
Sleep(20-waittime)
except
end;
until Terminated;
end;
All these possible solutions have the same problem - voice flow stops being continuous and you distinctively hear clicks during the playback especially if you have two or more calls. The only reason I can imagine is that timeSetEvent is more accurate than others. What can be done here?
Given that you have pinpointed a limit in the number of timers, a small design change to keep within that limit seems in order. Each timer currently does a negligible amount of work when procedure Timer20ms is called. So it seems feasible to allow a single timer to set multiple events.
As a first pass I'd try using only a single timer to set all the events.
I doubt this will be a solution because it's unlikely that signalling (resuming) a large number of TMicrophoneThread instances simultaneously won't cause other problems. But it will be useful to see how many can be handled smoothly (let's call it simultaneous-signal-limit); as this will likely be a factor in determining a hard limit before you need to look at scaling to better/more hardware.
constructor TMicrophoneThread.Create(ACall: TCall);
begin
inherited;
FCall:=ACall;
FEvent := CreateEvent(nil, False, False, nil);
{ Instead of setting a new timer, add the event to a list. }
TimerEvents.Add(FEvent);
end;
destructor TMicrophoneThread.Destroy;
begin
{ Instead of removing the timer, remove the event }
TimerEvents.Remove(FEvent);
CloseHandle(FEvent);
inherited;
end;
procedure Timer20ms(uTimerID, uMessage: UINT; dwUser, dw1, dw2: DWORD_PTR); stdcall;
{ The timer callback sets all events in the list. }
var
LTimers: TList;
begin
{ I'm illustrating this code where TimerEvents is implemented as a TThreadList.
If you can ensure all access to the list happens from the same thread,
you'll be able to do away with the locks - which would be better. }
LTimers := TThreadList(dwUser).LockList;
try
for LoopI := 0 to LTimers.Count - 1 do
SetEvent(THandle(LTimers[LoopI]));
finally
TThreadList(dwUser).UnlockList;
end;
end;
Once this experiment is out the way, you could look at running multiple timers. Each with its own list. If you stagger the timers, and manage to get a reasonably fair distribution of TMicrophoneThread instances across each timer; you may be able to get close to handling 16 x simultaneous-signal-limit instance of TMicrophoneThread.
Related
I would like to write a loop that checks the value of a variable has changed. There's no event that fires to tell me the value has changed.
The application doesn't support multi threading.
How to achieve this without causing app to freeze ?
The aim is this:
Application starts
...
loop
Check variable value
If changed then
exit
if timedOut then
exit
While loop causes application to freeze.
Thank you.
* Edit *
This is what I'm after (this code is written by Remy Lebeau):
const
APPWM_COM_EVENT_DONE = WM_APP + 1;
APPWM_COM_EVENT_TIMEOUT = WM_APP + 2;
type
MyClass = class
private
MsgWnd: HWND;
procedure COMEventHandler(parameters);
procedure WndProc(var Message: TMessage);
public
constructor Create;
destructor Destroy; override;
procedure DoIt;
end;
constructor MyClass.Create;
begin
inherited;
MsgWnd := AllocateHWnd(WndProc);
end
destructor MyClass.Destroy;
begin
KillTimer(MsgWnd, 1);
DeallocateHWnd(MsgWnd);
inherited;
end;
procedure MyClass.COMEventHandler(parameters);
begin
KillTimer(MsgWnd, 1);
PostMessage(MsgWnd, APPWM_COM_EVENT_DONE, 0, 0);
end;
procedure MyTimer(hWnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD); stdcall;
begin
KillTimer(hWnd, idEvent);
PostMessage(hWnd, APPWM_COM_EVENT_TIMEOUT, 0, 0);
end;
procedure MyClass.WndProc(var Message: TMessage);
begin
case Message.Msg of
APPWM_COM_EVENT_DONE:
begin
// Event fired, all good
end;
APPWM_COM_EVENT_TIMEOUT:
begin
// Event timed out
end;
else
begin
Message.Result := DefWindowProc(MsgWnd, Message.Msg, Message.WParam, Message.LParam);
end;
end;
end;
procedure MyClass.DoIt;
begin
SetTimer(MsgWnd, 1, 1000 * 1000, #MyTimer);
// invoke COM function that will eventually trigger the COM event...
end;
How to call DoIt and wait for either Event to fire or timeout without causing the application to freeze ?
Tried using while do loop but that prevented WndProc from running.
Thank you
Answer depends on your application demands. There are 2 easy solutions with prons and cons each:
1. Put Timer to application and check value by timeout. Dignity - it is the most easy way for GUI application (Windows messages loop already exists), drawback on other side - there will be delta time of detecting value have been changed.
2. Handle Application.OnIdle event. Disadvantage of this approach - yor checking procedure will be runned if nobody click on GUI elements.
Professional way to solve your solution - wrap your variable by complex object, for example:
Trigger = class
private
FOnChanged: TNotifyEvent;
public
procedure Emit;
property OnChanged: TNotifyEvent read FOnChanged write FOnChanged;
end;
procedure Trigger.Emit;
if Assined(FOnChanged) then
FOnChanged(Self)
end;
Cause of your application has not threads we can implement Trigger without mutexes/critical sections, on another side you can handle changing as soon as event producer will raise Emit
Good approach if you don't want use multithreading is split your ligic on multiple state machines based on coroutines.
Example based on AIO framework https://github.com/Purik/AIO
AIO framework create itself events loop, scheduling multiple state machines in parallel without threads:
program TriggerExample;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
SyncObjs,
Gevent,
Greenlets;
const
WAIT_TMEOUT_MSEC = 1000;
var
ChangedEvent: TGevent;
Value: Boolean = False;
// Part of application that raise change events randomly
procedure EventsProducer;
begin
while True do
begin
Greenlets.GreenSleep(100+Random(10000));
Value := True;
ChangedEvent.SetEvent;
end;
end;
begin
ChangedEvent := TGevent.Create(False, False);
// run fake event producer inside other state machine
TSymmetric.Spawn(EventsProducer);
// Loop
while True do
begin
if ChangedEvent.WaitFor(WAIT_TMEOUT_MSEC) = wrSignaled then
begin
WriteLn('Value was changed');
Value := False
end
else
begin
WriteLn('Exit by timeout');
end;
end;
end.
I use this method to process the image, but if it contains high-resolution images, more than 1000 x 1000 pixels, the image processing takes a very long time and cause the application not responding for a while, how to overcome it.
when processing high resolution images always appear Not Responding messages as in the picture.
type
TRGBArray = array[0..0] of TRGBTriple;
pRGBArray = ^TRGBArray;
var
ARL, ALL, AOL : pRGBarray;
TOGfx, TRGfx, TLGfx : TBitmap;
procedure TFZN.GfXColorProcessor;
var
X, Y : integer;
begin
TOGfx.Assign(TRGfx);
for Y := 0 to TRGfx.Height - 1 do
begin
ARL := TOGfx.Scanline[Y];
AOL := TLGfx.Scanline[Y];
//-------------------------
for x := 0 to TRGfx.Width - 1 do
begin
ARL[x].RGBtRed := AOL[X].RGBtRed;
IBG.Picture.bitmap.Assign(TOGfx);
end;
end;
end;
You should go with ScanLine(), as TLama suggested, and if it still takes long period to process the image, you can make the code threaded and continue the normal flow of application, or show a progress bar and force the user to wait. Keep in mind that playing with VCL controls outside of main thread isn't thread safe, so it's probably best to show some kind of notification to user that he should wait for processing to be finished.
Here is sample code of simple thread that does processing:
unit uImageProcessingThread;
interface
uses
Winapi.Windows, System.Classes, Vcl.Graphics;
type
TImageProcessingThread = class(TThread)
private
FBitmap: TBitmap;
protected
procedure Execute; override;
public
constructor Create(const ABitmap: TBitmap);
end;
implementation
constructor TImageProcessingThread.Create(const ABitmap: TBitmap);
begin
inherited Create(TRUE);
FBitmap := ABitmap;
end;
procedure TImageProcessingThread.Execute;
var
GC : LongInt;
H, W: Integer;
begin
for H := 0 to FBitmap.Height do
begin
for W := 0 to FBitmap.Width do
begin
GC := ColorToRGB(FBitmap.Canvas.Pixels[W, H]);
FBitmap.Canvas.Pixels[W, H] := RGB(GC, GC, GC);
end;
end;
end;
end.
There are couple flaws in your GfxColorProcessor() procedure:
1) It is bad practice to declare variables as global if it's not needed. ARL and AOL should be declared inside procedure. Do you use ALL variable? If not, there is no need for it to be declared. I'm not sure about TOGfx and TLGfx variables, but if you use them only inside GfxColorProcessor() procedure, then you should declare them inside that procedure as well.
2) You're risking access violation if TLGfx bitmap has smaller height or width than TRGfx one, since you would try to ScanLine[] the line number that doesn't exist or write out of range in ARL buffer.
3) Main bottleneck in your procedure is IBG.Picture.bitmap.Assign(TOGfx); line. You should execute it after processing, not during processing. By doing this, you would call IBG.Assign() only once, instead over 1.000.000 times (X*Y).
So, your procedure should look like this. I'm assuming you want to assign TLGfx pixels red value to TRGfx ones, and then assign the new image to IBG bitmap, while leaving TRGfx and TLGfx untouched:
type
TRGBArray = array[0..0] of TRGBTriple;
PRGBArray = ^TRGBArray;
var
TRGfx, TLGfx: TBitmap;
procedure TFZN.GfXColorProcessor;
var
X, Y : Integer;
ARL, AOL: PRGBArray;
tmp : TBitmap;
begin
Assert((TRGfx.Width = TLGfx.Width) and (TRGfx.Height = TLGfx.Height),
'Image sizes are not equal!');
tmp := TBitmap.Create;
try
tmp.Assign(TRGfx);
for Y := 0 to tmp.Height - 1 do
begin
ARL := tmp.ScanLine[Y];
AOL := TLGfx.ScanLine[Y];
for X := 0 to tmp.Width - 1 do
ARL[X].rgbtRed := AOL[X].rgbtRed;
end;
IBG.Picture.Bitmap.Assign(tmp);
finally
tmp.Free;
end;
end;
A very simple approach to fix this problem is to call Application.ProcessMessages inside your loops. This method will let windows to process all the messages still pending and then return to your code.
During the message processing, events will be fired, for isntance, clicks will happen. One of those clicks may happen on a button used to set a variable that indicates that the process should be aborted.
I hope this helps.
Dear Delphi programmers,
I'm looking for help how to write a one-shot timer (No GUI, so VCL Timers out of question)...
Let me explain a little bit more.
In my code (explaining with VCL timer but in this particular project I have no forms):
Call a procedure which send a char over serial port
Enable a timer with a X amount of Interval
In the OnTimer event:
I have a code which send a char then disable the timer itself to never be executed again.
The problem is that I need to make the creation of these timers dynamic.
I thought of the function SetTimer() then KillTimer() in the "OnTimer event" to disable it (free it).
Is it a good (safe) way?
Thank you!
Is it safe to kill timer from inside of a timer event ?
Yes, that's perfectly safe.
How to implement simplest one shot timer ?
The easiest implementation of a 1 second one shot timer is this, but note, that if you start more of them, you won't be able to distinguish which one of them elapsed its interval:
procedure TimerProc(hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR;
dwTime: DWORD); stdcall;
begin
KillTimer(0, idEvent);
ShowMessage('I''m done!');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
SetTimer(0, 0, 1000, #TimerProc);
end;
The multimedia timer API provides support for a one shot timer. The benefit is, that the timing is much more precise than the SetTimer/KillTimer solution and you can use it with intervals <50 ms. This comes at a price, as the callback does not return in the context of the main thread.
Here is my implementation of a one-shot timer using the multimedia timer API:
unit MMTimer;
interface
uses windows, Classes, mmsystem, SysUtils;
TOneShotCallbackEvent = procedure (const UserData: Pointer) of object;
(*
The MMOneShotCallback function calls the Callback after the Interval passed.
** Attention: **
The Callback is not called within the context of the main thread.
*)
type TMMOneShotTimer = class(TObject)
private
FTimeCaps: TTimeCaps;
FResult: Integer;
FResolution: Cardinal;
public
constructor Create;
function MMOneShotCallback(const Interval: Cardinal; UserData: Pointer; Callback: TOneShotCallbackEvent): Boolean;
property Result: Integer read FResult;
property Resolution: Cardinal read FResolution;
end;
implementation
type
TOneShotCallbackData = record
Callback: TOneShotCallbackEvent;
UserData: Pointer;
end;
POneShotCallbackData = ^TOneShotCallbackData;
procedure OneShotCallback(TimerID, Msg: UINT;
dwUser, dw1, dw2: DWord); pascal;
var pdata: POneShotCallbackData;
begin
pdata := Pointer(dwUser);
pdata.Callback(pdata.UserData);
FreeMemory(pdata);
end;
constructor TMMOneShotTimer.Create;
begin
FResult := timeGetDevCaps(#FTimeCaps, SizeOF(FTimeCaps));
Assert(FResult=TIMERR_NOERROR, 'Call to timeGetDevCaps failed');
FResolution := FTimeCaps.wPeriodMin;
FResult := timeBeginPeriod(FResolution);
Assert(FResult=TIMERR_NOERROR, 'Call to timeBeginPeriod failed');
end;
function TMMOneShotTimer.MMOneShotCallback(const Interval: Cardinal; UserData: Pointer; Callback: TOneShotCallbackEvent): Boolean;
var pdata: POneShotCallbackData;
begin
GetMem(pdata, SizeOf(TOneShotCallbackData));
pdata.Callback := Callback;
pdata.UserData := UserData;
result := (0 <> timeSetEvent(Interval, FResolution, #OneShotCallback, DWord(pdata), TIME_ONESHOT));
if not result then
FreeMemory(pdata);
end;
end.
Do you realize, that you do not have to have a GUI, to use a VCL timer as long as you do have a window handle? You can simply instantiate one from the code by
fTimer := TTimer.Create(hWindowHandle);
And even if you don't have a window handle You can create one by calling
fVirtualWindowHWND := AllocateHWnd(WndMethod);
but in that case you also have to write your own message loop.
I know that calling the Windows API seems to be an easier solution, but it also has its own caveeats (like you can not pass a class method to it...), and I tought, You might want to know about this one.
I use Delphi 7 and my project has several non modal visible forms. The problem is if in one of them MessageBoxEx is called all actions of the application are not updated until MessageBoxEx’s form is closed. In my project it can broke business logic of application.
The TApplication.HandleMessage method is never called while MessageBoxEx's window is shown so it doesn’t call the DoActionIdle and Actions are not updated.
I think what I need is to catch a state of my application when it’s idle and update states of all actions.
First I implemented TApplication. OnIdle handler:
procedure TKernel.OnIdle(Sender: TObject; var Done: Boolean);
begin
{It’s only to switch off the standard updating from TApplication.Idle. It's to make the CPU usage lower while MessageBoxEx's window isn't shown }
Done := False;
end;
implementation
var
MsgHook: HHOOK;
{Here is a hook}
function GetMsgHook(nCode: Integer; wParam: Longint; var Msg: TMsg): Longint; stdcall;
var
m: TMsg;
begin
Result := CallNextHookEx(MsgHook, nCode, wParam, Longint(#Msg));
if (nCode >= 0) and (_instance <> nil) then
begin
{If there aren’t the messages in the application's message queue then the application is in idle state.}
if not PeekMessage(m, 0, 0, 0, PM_NOREMOVE) then
begin
_instance.DoActionIdle;
WaitMessage;
end;
end;
end;
initialization
MsgHook := SetWindowsHookEx(WH_GETMESSAGE, #GetMsgHook, 0, GetCurrentThreadID);
finalization
if MsgHook <> 0 then
UnhookWindowsHookEx(MsgHook);
Here is a method for updating states of all actions of the application. It’s just a modified version of TApplication.DoActionIdle:
type
TCustomFormAccess = class(TCustomForm);
procedure TKernel.DoActionIdle;
var
i: Integer;
begin
for I := 0 to Screen.CustomFormCount - 1 do
with Screen.CustomForms[i] do
if HandleAllocated and IsWindowVisible(Handle) and
IsWindowEnabled(Handle) then
TCustomFormAccess(Screen.CustomForms[i]).UpdateActions;
end;
It seems that the updating of the states happens much often than usually (I’m going to find out where is a problem using profiler).
Besides, CPU usage grows seriously when the mouse’s cursor is not over the application’s windows (about 25% on my DualCore Pentium).
What do you think about my problem and the way I try to solve it? Is it a good idea to use hooks or there is a better way to catch the application idle state? Do I rather need to use WH_CALLWNDPROCRET during setting the hook?
Why MessageBoxEx blocks TApplication.HandleMessage? Is there way to prevent this behavior? I’ve tried to call it with MB_APPLMODAL, MB_SYSTEMMODAL, MB_TASKMODAL flags but it didn’t help.
MessageBox/Ex() is a modal dialog, and as such it runs its own message loop internally since the calling thread's normal message loop is blocked. MessageBox/Ex() receives any messages that are in the calling thread's message queue, and will dispatch them to target windows normally (so things like window-based timers still work, such as TTimer), but its modal message loop has no concept of VCL-specific messages, like action upates, and will discard them. TApplication.HandleMessage() is only called by the main VCL message loop, the TApplication.ProcessMessages() method, and the TForm.ShowModal() method (this is why modal VCL Form windows do not suffer from this problem), none of which are called while MessageBox/Ex() is running (the same will be true for any OS modal dialog).
To solve your problem, you have a couple of choices:
set a thread-local message hook via SetWindowsHookEx() right before calling MessageBox/Ex(), then release the hook right after MessageBox/Ex() exits. This allows you to look at every message that MessageBox/Ex() receives and dispatch them to VCL handlers as needed. DO NOT call PeekMessage(), GetMessage() or WaitMessage() inside of a message hook!
type
TApplicationAccess = class(TApplication)
end;
function GetMsgHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
Msg: TMsg;
begin
if (nCode >= 0) and (wParam = PM_REMOVE) then
begin
Msg := PMsg(lParam)^;
with TApplicationAccess(Application) do begin
if (not IsPreProcessMessage(Msg))
and (not IsHintMsg(Msg))
and (not IsMDIMsg(Msg))
and (not IsKeyMsg(Msg))
and (not IsDlgMsg(Msg)) then
begin
end;
end;
end;
Result := CallNextHookEx(MsgHook, nCode, wParam, lParam);
end;
function DoMessageBoxEx(...): Integer;
var
MsgHook: HHOOK;
begin
MsgHook := SetWindowsHookEx(WH_GETMESSAGE, #GetMsgHook, 0, GetCurrentThreadID);
Result := MessageBoxEx(...);
if MsgHook <> 0 then UnhookWindowsHookEx(MsgHook);
end;
move the MessageBox/Ex() call to a separate worker thread so the calling thread is free to process messages normally. If you need to wait for the result of MessageBox/Ex(), such as when prompting the user for input, then you can use MsgWaitForMultipleObjects() to wait for the thread to terminate while allowing the waiting thread to call Application.ProcessMessages() whenever there are pending messages to process.
type
TMessageBoxThread = class(TThread)
protected
procedure Execute; override;
...
public
constructor Create(...);
end;
constructor TMessageBoxThread.Create(...);
begin
inherited Create(False);
...
end;
function TMessageBoxThread.Execute;
begin
ReturnValue := MessageBoxEx(...);
end;
function DoMessageBoxEx(...): Integer;
var
Thread: TMessageBoxThread;
WaitResult: DWORD;
begin
Thread := TMessageBoxThread.Create(...);
try
repeat
WaitResult := MsgWaitForMultipleObjects(1, Thread.Handle, False, INFINITE, QS_ALLINPUT);
if WaitResult = WAIT_FAILED then RaiseLastOSError;
if WaitResult = WAIT_OBJECT_0 + 1 then Application.ProcessMessages;
until WaitResult = WAIT_OBJECT_0;
Result := Thread.ReturnVal;
finally
Thread.Free;
end;
end;
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 sometimes like to be able to use, for example, non-visual components like TTimer from a background thread. Or indeed just create a hidden window. This is not safe because of the reliance on AllocateHwnd. Now, AllocateHwnd is not threadsafe which I understand is by design.
Is there an easy solution that allows me to use AllocateHwnd from a background thread?
This problem can be solved like so:
Obtain or implement a threadsafe version of AllocateHwnd and DeallocateHwnd.
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.
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.
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.