1 { Copyright (C) 2005 Bas Steendijk and Peter Green
\r 
   2   For conditions of distribution and use, see copyright notice in zlib_license.txt
\r 
   3   which is included in the package
\r 
   4   ----------------------------------------------------------------------------- }
\r 
   6 //this unit provides a rough approximation of windows messages on linux
\r 
   7 //it is useful for multithreaded applications on linux to communicate back to
\r 
   8 //the main lcore thread
\r 
   9 //This unit is *nix only, on windows you should use the real thing
\r 
  12 //windows messages like system based on lcore tasks
\r 
  15 uses pgtypes,sysutils,bsearchtree,strings,syncobjs;
\r 
  18 {$if (fpc_version < 2) or ((fpc_version=2) and ((fpc_release < 2) or ((fpc_release = 2) and (fpc_patch < 2)) ))}
\r 
  19   {$error this code is only supported under fpc 2.2.2 and above due to bugs in the eventobject code in older versions}
\r 
  29   hwnd=qword; //window handles are monotonically increasing 64 bit integers,
\r 
  30               //this should allow for a million windows per second for over half
\r 
  33   twndproc=function(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r 
  38     lpfnwndproc : twndproc;
\r 
  39     cbclsextra : integer;
\r 
  40     cbwndextra : integer;
\r 
  41     hinstance : thinstance;
\r 
  44     hbrbackground : hbrush;
\r 
  45     lpszmenuname : pchar;
\r 
  46     lpszclassname : pchar;
\r 
  48   PWNDCLASS=^twndclass;
\r 
  52   tTIMERPROC = procedure (ahwnd:HWND; umsg:integer; idevent:taddrint;dwtime:taddrint);stdcall;
\r 
  72   THevent=TEventObject;
\r 
  74   WS_EX_TOOLWINDOW = $80;
\r 
  75   WS_POPUP = longint($80000000);
\r 
  76   CW_USEDEFAULT=$80000000;
\r 
  81   INFINITE = syncobjs.infinite;
\r 
  83 function getwindowlongptr(ahwnd:hwnd;nindex:integer) : taddrint;
\r 
  84 function setwindowlongptr(ahwnd:hwnd;nindex:integer;dwNewLong : taddrint) : taddrint;
\r 
  85 function DefWindowProc(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r 
  86 function RegisterClass(const lpWndClass:TWNDCLASS):ATOM;
\r 
  87 function CreateWindowEx(dwExStyle:DWORD; lpClassName:LPCSTR; lpWindowName:LPCSTR; dwStyle:DWORD; X:longint;Y:longint; nWidth:longint; nHeight:longint; hWndParent:HWND; hMenu:HMENU;hInstance:HINST; lpParam:LPVOID):HWND;
\r 
  88 function DestroyWindow(ahWnd:HWND):WINBOOL;
\r 
  89 function PostMessage(hWnd:HWND; Msg:UINT; wParam:WPARAM; lParam:LPARAM):WINBOOL;
\r 
  90 function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): WINBOOL;
\r 
  91 function DispatchMessage(const lpMsg: TMsg): Longint;
\r 
  92 function GetMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT): WINBOOL;
\r 
  93 function SetEvent(hEvent:THevent):WINBOOL;
\r 
  94 function CreateEvent(lpEventAttributes:PSECURITYATTRIBUTES; bManualReset:WINBOOL; bInitialState:WINBOOL; lpName:pchar):tHevent;
\r 
  95 function terminatethread(threadhandle : tthreadid;dummy:integer) : boolean;
\r 
  96 function waitforsingleevent(event:thevent;timeout:cardinal) : twaitresult;
\r 
  97 function SetTimer(ahWnd:HWND; nIDEvent:taddrint; uElapse:UINT; lpTimerFunc:tTIMERPROC):UINT;
\r 
  98 function KillTimer(ahWnd:HWND; uIDEvent:taddrint):WINBOOL;
\r 
 104   baseunix,unix,lcore,unixutil,ltimevalstuff,sockets;//,safewriteln;
\r 
 109   tmessageintransit = class
\r 
 111     next : tmessageintransit;
\r 
 114   tthreaddata = class
\r 
 115     messagequeue : tmessageintransit;
\r 
 116     messageevent : teventobject;
\r 
 118     lcorethread : boolean;
\r 
 119     nexttimer : ttimeval;
\r 
 120     threadid : tthreadid;
\r 
 124     extrawindowmemory : pointer;
\r 
 125     threadid : tthreadid;
\r 
 126     windowproc : twndproc;
\r 
 130   structurelock : tcriticalsection;
\r 
 131   threaddata : thashtable;
\r 
 132   windowclasses : thashtable;
\r 
 133   lcorelinkpipesend : integer;
\r 
 134   lcorelinkpiperecv : tlasio;
\r 
 135   windows : thashtable;
\r 
 136   //I would rather things crash immediately
\r 
 137   //if they use an insufficient size type
\r 
 138   //than crash after over four billion
\r 
 139   //windows have been made ;)
\r 
 140   nextwindowhandle : qword = $100000000;
\r 
 143 //findthreaddata should only be called while holding the structurelock
\r 
 144 function findthreaddata(threadid : tthreadid) : tthreaddata;
\r 
 146   result := tthreaddata(findtree(@threaddata,inttostr(taddrint(threadid))));
\r 
 147   if result = nil then begin
\r 
 148     result := tthreaddata.create;
\r 
 149     result.messageevent := teventobject.create(nil,false,false,inttostr(taddrint(result)));
\r 
 150     result.nexttimer := tv_invalidtimebig;
\r 
 151     result.threadid := threadid;
\r 
 152     addtree(@threaddata,inttostr(taddrint(threadid)),result);
\r 
 156 //deletethreaddataifunused should only be called while holding the structurelock
\r 
 157 procedure deletethreaddataifunused(athreaddata : tthreaddata);
\r 
 159   //writeln('in deletethreaddataifunused');
\r 
 160   if (athreaddata <> nil) then if (athreaddata.waiting=false) and (athreaddata.messagequeue=nil) and (athreaddata.lcorethread=false) and (athreaddata.nexttimer.tv_sec=tv_invalidtimebig.tv_sec) and (athreaddata.nexttimer.tv_usec=tv_invalidtimebig.tv_usec) then begin
\r 
 161     //writeln('threaddata is unused, freeing messageevent');
\r 
 162     athreaddata.messageevent.free;
\r 
 163     //writeln('freeing thread data object');
\r 
 165     //writeln('deleting thread data object from hashtable');
\r 
 166     deltree(@threaddata,inttostr(taddrint(athreaddata.threadid)));
\r 
 167     //writeln('finished deleting thread data');
\r 
 169     //writeln('thread data is not unused');
\r 
 173 function getwindowlongptr(ahwnd:hwnd;nindex:integer) : taddrint;
\r 
 177   structurelock.acquire;
\r 
 179     window := findtree(@windows,inttostr(ahwnd));
\r 
 180     if window <> nil then begin
\r 
 181       result := paddrint(taddrint(window.extrawindowmemory)+nindex)^;
\r 
 186     structurelock.release;
\r 
 190 function setwindowlongptr(ahwnd:hwnd;nindex:integer;dwNewLong : taddrint) : taddrint;
\r 
 194   structurelock.acquire;
\r 
 196     window := findtree(@windows,inttostr(ahwnd));
\r 
 197     if window <> nil then begin
\r 
 198       result := paddrint(taddrint(window.extrawindowmemory)+nindex)^;
\r 
 199       paddrint(taddrint(window.extrawindowmemory)+nindex)^ := dwnewlong;
\r 
 204     structurelock.release;
\r 
 210 function DefWindowProc(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r 
 215 function strdup(s:pchar) : pchar;
\r 
 217   //swriteln('in strdup, about to allocate memory');
\r 
 218   result := getmem(strlen(s)+1);
\r 
 219   //swriteln('about to copy string');
\r 
 221   //swriteln('leaving strdup');
\r 
 224 function RegisterClass(const lpWndClass:TWNDCLASS):ATOM;
\r 
 226   storedwindowclass:pwndclass;
\r 
 228   structurelock.acquire;
\r 
 230     //swriteln('in registerclass, about to check for duplicate window class');
\r 
 231     storedwindowclass := findtree(@windowclasses, lpwndclass.lpszclassname);
\r 
 232     if storedwindowclass <> nil then begin
\r 
 234       if comparebyte(storedwindowclass^,lpwndclass,sizeof(twndclass)-sizeof(pchar)-sizeof(pchar)) <> 0 then begin
\r 
 235         //swriteln('duplicate window class registered with different settings');
\r 
 236         raise exception.create('duplicate window class registered with different settings');
\r 
 238         //swriteln('duplicate window class registered with same settings, tolerated');
\r 
 241       //swriteln('about to allocate memory for new windowclass');
\r 
 242       storedwindowclass := getmem(sizeof(twndclass));
\r 
 243       //swriteln('about to copy windowclass from parameter');
\r 
 244       move(lpwndclass,storedwindowclass^,sizeof(twndclass));
\r 
 245       //swriteln('about to copy strings');
\r 
 246       if lpwndclass.lpszmenuname <> nil then storedwindowclass.lpszmenuname := strdup(lpwndclass.lpszmenuname);
\r 
 247       if lpwndclass.lpszclassname <> nil then storedwindowclass.lpszclassname := strdup(lpwndclass.lpszclassname);
\r 
 248       //swriteln('about to add result to list of windowclasses');
\r 
 249       addtree(@windowclasses,lpwndclass.lpszclassname,storedwindowclass);
\r 
 251     //swriteln('about to return result');
\r 
 252     result := storedwindowclass;
\r 
 253     //swriteln('leaving registerclass');
\r 
 255     structurelock.release;
\r 
 259 function CreateWindowEx(dwExStyle:DWORD; lpClassName:LPCSTR; lpWindowName:LPCSTR; dwStyle:DWORD; X:longint;Y:longint; nWidth:longint; nHeight:longint; hWndParent:HWND; hMenu:HMENU;hInstance:HINST; lpParam:LPVOID):HWND;
\r 
 261   wndclass : pwndclass;
\r 
 262   tm : tthreadmanager;
\r 
 265   structurelock.acquire;
\r 
 267     window := twindow.create;
\r 
 268     window.hwnd := nextwindowhandle;
\r 
 269     result := window.hwnd;
\r 
 270     nextwindowhandle := nextwindowhandle + 1;
\r 
 271     addtree(@windows,inttostr(window.hwnd),window);
\r 
 272     wndclass := findtree(@windowclasses,lpclassname);
\r 
 273     window.extrawindowmemory := getmem(wndclass.cbwndextra);
\r 
 275     getthreadmanager(tm);
\r 
 276     window.threadid := tm.GetCurrentThreadId;
\r 
 277     window.windowproc := wndclass.lpfnwndproc;
\r 
 279     structurelock.release;
\r 
 282 function DestroyWindow(ahWnd:HWND):WINBOOL;
\r 
 285   windowthreaddata : tthreaddata;
\r 
 286   currentmessage : tmessageintransit;
\r 
 287   prevmessage : tmessageintransit;
\r 
 289   //writeln('started to destroy window');
\r 
 290   structurelock.acquire;
\r 
 292     window := twindow(findtree(@windows,inttostr(ahwnd)));
\r 
 293     if window <> nil then begin
\r 
 294       freemem(window.extrawindowmemory);
\r 
 295       //writeln('about to delete window from windows structure');
\r 
 296       deltree(@windows,inttostr(ahwnd));
\r 
 297       //writeln('deleted window from windows structure');
\r 
 298       windowthreaddata := tthreaddata(findtree(@threaddata,inttostr(taddrint(window.threadid))));
\r 
 300       if windowthreaddata <> nil then begin
\r 
 301         //writeln('found thread data scanning for messages to clean up');
\r 
 302         currentmessage := windowthreaddata.messagequeue;
\r 
 303         prevmessage := nil;
\r 
 304         while currentmessage <> nil do begin
\r 
 305           while (currentmessage <> nil) and (currentmessage.msg.hwnd = ahwnd) do begin
\r 
 306             if prevmessage = nil then begin
\r 
 307               windowthreaddata.messagequeue := currentmessage.next;
\r 
 309               prevmessage.next := currentmessage.next;
\r 
 311             currentmessage.free;
\r 
 312             if prevmessage = nil then begin
\r 
 313               currentmessage := windowthreaddata.messagequeue;
\r 
 315               currentmessage := prevmessage.next;
\r 
 318           if currentmessage <> nil then begin
\r 
 319             prevmessage := currentmessage;
\r 
 320             currentmessage := currentmessage.next;
\r 
 323         //writeln('deleting thread data structure if it is unused');
\r 
 324         deletethreaddataifunused(windowthreaddata);
\r 
 326         //writeln('there is no thread data to search for messages to cleanup');
\r 
 328       //writeln('freeing window');
\r 
 335     structurelock.release;
\r 
 337   //writeln('window destroyed');
\r 
 342 function PostMessage(hWnd:HWND; Msg:UINT; wParam:WPARAM; lParam:LPARAM):WINBOOL;
\r 
 344   threaddata : tthreaddata;
\r 
 345   message : tmessageintransit;
\r 
 346   messagequeueend : tmessageintransit;
\r 
 349   structurelock.acquire;
\r 
 351     window := findtree(@windows,inttostr(hwnd));
\r 
 352     if window <> nil then begin
\r 
 353       threaddata := findthreaddata(window.threadid);
\r 
 354       message := tmessageintransit.create;
\r 
 355       message.msg.hwnd := hwnd;
\r 
 356       message.msg.message := msg;
\r 
 357       message.msg.wparam := wparam;
\r 
 358       message.msg.lparam := lparam;
\r 
 359       if threaddata.lcorethread then begin
\r 
 360         //swriteln('posting message to lcore thread');
\r 
 361         fdwrite(lcorelinkpipesend,message,sizeof(message));
\r 
 363         //writeln('posting message to non lcore thread');
\r 
 364         if threaddata.messagequeue = nil then begin
\r 
 365           threaddata.messagequeue := message;
\r 
 367           messagequeueend := threaddata.messagequeue;
\r 
 368           while messagequeueend.next <> nil do begin
\r 
 369             messagequeueend := messagequeueend.next;
\r 
 371           messagequeueend.next := message;
\r 
 374         //writeln('message added to queue');
\r 
 375         if threaddata.waiting then threaddata.messageevent.setevent;
\r 
 382     structurelock.release;
\r 
 387 function gettickcount : dword;
\r 
 392   gettimemonotonic(tv);
\r 
 393   result64 := (tv.tv_sec*1000)+(tv.tv_usec div 1000);
\r 
 394   result := result64;
\r 
 397 function DispatchMessage(const lpMsg: TMsg): Longint;
\r 
 399   timerproc : ttimerproc;
\r 
 401   windowproc : twndproc;
\r 
 403   ////swriteln('in dispatchmessage, msg.hwnd='+inttohex(taddrint(lpmsg.hwnd),16));
\r 
 404   if (lpmsg.lparam <> 0) and (lpmsg.message = WM_TIMER) then begin
\r 
 405     timerproc := ttimerproc(lpmsg.lparam);
\r 
 406     timerproc(lpmsg.hwnd,lpmsg.message,lpmsg.wparam,gettickcount);
\r 
 409     structurelock.acquire;
\r 
 411       window := findtree(@windows,inttostr(lpmsg.hwnd));
\r 
 412       //we have to get the window procedure while the structurelock
\r 
 413       //is still held as the window could be destroyed from another thread
\r 
 415       if window <> nil then begin
\r 
 416         windowproc := window.windowproc;
\r 
 421       structurelock.release;
\r 
 423     if assigned(windowproc) then begin
\r 
 424       result := windowproc(lpmsg.hwnd,lpmsg.message,lpmsg.wparam,lpmsg.lparam);
\r 
 431 procedure processtimers;
\r 
 435 function GetMessageinternal(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT; wremovemsg : UINT;peek:boolean): WINBOOL;
\r 
 437   tm : tthreadmanager;
\r 
 438   threaddata : tthreaddata;
\r 
 439   message : tmessageintransit;
\r 
 441   timeouttv : ttimeval;
\r 
 445   if hwnd <> 0 then raise exception.create('getting messages for an individual window is not supported');
\r 
 446   if (wmsgfiltermin <> 0) or (wmsgfiltermax <> 0) then raise exception.create('message filtering is not supported');
\r 
 447   structurelock.acquire;
\r 
 450     getthreadmanager(tm);
\r 
 451     threaddata := findthreaddata(tm.GetCurrentThreadId);
\r 
 452     if threaddata.lcorethread then raise exception.create('get/peek message cannot be used in the lcore thread');
\r 
 453     message := threaddata.messagequeue;
\r 
 454     gettimemonotonic(nowtv);
\r 
 455     while (not peek) and (message=nil) and (not tv_compare(nowtv,threaddata.nexttimer)) do begin
\r 
 456       threaddata.waiting := true;
\r 
 457       structurelock.release;
\r 
 458       if (threaddata.nexttimer.tv_sec = TV_invalidtimebig.tv_sec) and (threaddata.nexttimer.tv_usec = TV_invalidtimebig.tv_usec) then begin
\r 
 459         threaddata.messageevent.waitfor(INFINITE);
\r 
 462         timeouttv := threaddata.nexttimer;
\r 
 463         timeoutms := (timeouttv.tv_sec * 1000)+(timeouttv.tv_usec div 1000);
\r 
 464         //i am assuming the timeout is in milliseconds
\r 
 465         if (timeoutms > maxlongint) then timeoutms := maxlongint;
\r 
 466         threaddata.messageevent.waitfor(timeoutms);
\r 
 469       structurelock.acquire;
\r 
 470       threaddata.waiting := false;
\r 
 471       message := threaddata.messagequeue;
\r 
 472       gettimemonotonic(nowtv);
\r 
 474     if (message=nil) and tv_compare(nowtv,threaddata.nexttimer) then begin
\r 
 477     message := threaddata.messagequeue;
\r 
 478     if message <> nil then begin
\r 
 479       lpmsg := message.msg;
\r 
 480       if wremovemsg=PM_REMOVE then begin
\r 
 481         threaddata.messagequeue := message.next;
\r 
 487     deletethreaddataifunused(threaddata);
\r 
 489     structurelock.release;
\r 
 493 function GetMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT): WINBOOL;
\r 
 495   result := getmessageinternal(lpmsg,hwnd,wmsgfiltermin,wmsgfiltermax,PM_REMOVE,false);
\r 
 498 function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): WINBOOL;
\r 
 500   result := getmessageinternal(lpmsg,hwnd,wmsgfiltermin,wmsgfiltermax,wRemoveMsg,true);
\r 
 503 function SetEvent(hEvent:THevent):WINBOOL;
\r 
 509 function CreateEvent(lpEventAttributes:PSECURITYATTRIBUTES; bManualReset:WINBOOL; bInitialState:WINBOOL; lpName:pchar):tHevent;
\r 
 511   result := teventobject.create(lpeventattributes,bmanualreset,binitialstate,lpname);
\r 
 514 function terminatethread(threadhandle:tthreadid;dummy : integer) : boolean;
\r 
 516   tm : tthreadmanager;
\r 
 518   getthreadmanager(tm);
\r 
 519   tm.killthread(threadhandle);
\r 
 523 function waitforsingleevent(event:thevent;timeout:cardinal) : twaitresult;
\r 
 525   result := event.waitfor(timeout);
\r 
 528 procedure removefrombuffer(n : integer; var buffer:string);
\r 
 530   if n=length(buffer) then begin
\r 
 533     uniquestring(buffer);
\r 
 534     move(buffer[n+1],buffer[1],length(buffer)-n);
\r 
 535     setlength(buffer,length(buffer)-n);
\r 
 541     procedure available(sender:tobject;error:word);
\r 
 547 procedure tsc.available(sender:tobject;error:word);
\r 
 549   message : tmessageintransit;
\r 
 550   messagebytes : array[1..sizeof(tmessageintransit)] of char absolute  message;
\r 
 553   //swriteln('received data on lcorelinkpipe');
\r 
 554   recvbuf := recvbuf + lcorelinkpiperecv.receivestr;
\r 
 555   while length(recvbuf) >= sizeof(tmessageintransit) do begin
\r 
 556     for i := 1 to sizeof(tmessageintransit) do begin
\r 
 557       messagebytes[i] := recvbuf[i];
\r 
 559     dispatchmessage(message.msg);
\r 
 561     removefrombuffer(sizeof(tmessageintransit),recvbuf);
\r 
 567   tm : tthreadmanager;
\r 
 568   threaddata : tthreaddata;
\r 
 569   pipeends : tfildes;
\r 
 572   structurelock := tcriticalsection.create;
\r 
 573   getthreadmanager(tm);
\r 
 574   threaddata := findthreaddata(tm.GetCurrentThreadId);
\r 
 575   threaddata.lcorethread := true;
\r 
 577   lcorelinkpipesend := pipeends[1];
\r 
 578   lcorelinkpiperecv := tlasio.create(nil);
\r 
 579   lcorelinkpiperecv.dup(pipeends[0]);
\r 
 580   lcorelinkpiperecv.ondataavailable := sc.available;
\r 
 585   lcorethreadtimers : thashtable;
\r 
 587   tltimerformsg = class(tltimer)
\r 
 591     procedure timer(sender : tobject);
\r 
 594 procedure tltimerformsg.timer(sender : tobject);
\r 
 598   ////swriteln('in tltimerformsg.timer');
\r 
 599   fillchar(msg,sizeof(msg),0);
\r 
 600   msg.message := WM_TIMER;
\r 
 604   dispatchmessage(msg);
\r 
 607 function SetTimer(ahWnd:HWND; nIDEvent:taddrint; uElapse:UINT; lpTimerFunc:tTIMERPROC):UINT;
\r 
 609   threaddata : tthreaddata;
\r 
 610   ltimer : tltimerformsg;
\r 
 611   tm : tthreadmanager;
\r 
 614   structurelock.acquire;
\r 
 616     window := findtree(@windows,inttostr(ahwnd));
\r 
 617     if window= nil then raise exception.create('invalid window');
\r 
 618     threaddata := findthreaddata(window.threadid);
\r 
 620     structurelock.release;
\r 
 622   if threaddata.lcorethread then begin
\r 
 623     getthreadmanager(tm);
\r 
 624     if tm.GetCurrentThreadId <> window.threadid then raise exception.create('timers on the lcore thread may only be added and removed from the lcore thread');
\r 
 625     if ahwnd = 0 then raise exception.create('timers on the lcore thread must be associated with a window handle');
\r 
 626     if @lptimerfunc <> nil then raise exception.create('seperate timer functions are not supported');
\r 
 628     //remove preexisting timer with same ID
\r 
 629     killtimer(ahwnd,nIDEvent);
\r 
 631     ltimer := tltimerformsg.create(nil);
\r 
 632     ltimer.interval := uelapse;
\r 
 633     ltimer.id := nidevent;
\r 
 634     ltimer.hwnd := ahwnd;
\r 
 635     ltimer.enabled := true;
\r 
 636     ltimer.ontimer := ltimer.timer;
\r 
 638     addtree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(nIDEvent),ltimer);
\r 
 640     result := nidevent;
\r 
 642     raise exception.create('settimer not implemented for threads other than the lcore thread');
\r 
 646 function KillTimer(ahWnd:HWND; uIDEvent:taddrint):WINBOOL;
\r 
 648   threaddata : tthreaddata;
\r 
 649   ltimer : tltimerformsg;
\r 
 650   tm : tthreadmanager;
\r 
 653   structurelock.acquire;
\r 
 655     window := findtree(@windows,inttostr(ahwnd));
\r 
 656     if window= nil then raise exception.create('invalid window');
\r 
 657     threaddata := findthreaddata(window.threadid);
\r 
 659     structurelock.release;
\r 
 661   if threaddata.lcorethread then begin
\r 
 662     getthreadmanager(tm);
\r 
 663     if tm.GetCurrentThreadId <> window.threadid then raise exception.create('timers on the lcore thread may only be added and remove from the lcore thread');
\r 
 664     if ahwnd = 0 then raise exception.create('timers on the lcore thread must be associated with a window handle');
\r 
 665     ltimer := tltimerformsg(findtree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(uIDEvent)));
\r 
 666     if ltimer <> nil then begin
\r 
 667       deltree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(uIDEvent));
\r 
 674     raise exception.create('settimer not implemented for threads other than the lcore thread');
\r