├── Example ├── DelphiTimerQueue.res ├── DelphiTimerQueue.dpr ├── DelphiTimerQueue.deployproj └── DelphiTimerQueue.dproj └── README.md /Example/DelphiTimerQueue.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grijjy/DelphiTimerQueue/HEAD/Example/DelphiTimerQueue.res -------------------------------------------------------------------------------- /Example/DelphiTimerQueue.dpr: -------------------------------------------------------------------------------- 1 | program DelphiTimerQueue; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | uses 8 | {$IFDEF MSWINDOWS} 9 | Windows, 10 | Grijjy.TimerQueue.Win, 11 | {$ENDIF} 12 | {$IFDEF LINUX} 13 | Posix.Pthread, 14 | Grijjy.TimerQueue.Linux, 15 | {$ENDIF} 16 | System.Generics.Collections, 17 | System.SyncObjs, 18 | System.SysUtils; 19 | 20 | type 21 | TMyClass = class(TObject) 22 | private 23 | FTimerQueue: TgoTimerQueue; 24 | private 25 | procedure OnTimer(const ASender: TObject); 26 | public 27 | constructor Create; 28 | destructor Destroy; override; 29 | end; 30 | 31 | var 32 | MyClass: TMyClass; 33 | 34 | procedure Log(const AText: String); 35 | begin 36 | Writeln(AText); 37 | end; 38 | 39 | { TMyClass } 40 | 41 | constructor TMyClass.Create; 42 | var 43 | I: Integer; 44 | Handle: THandle; 45 | begin 46 | FTimerQueue := TgoTimerQueue.Create; 47 | for I := 1 to 5 do // add 5 timers to the queue 48 | begin 49 | // set interval to 1000ms, and the event to OnTimer() 50 | Handle := FTimerQueue.Add(1000, OnTimer); 51 | Writeln(Format('Timer Added (Handle=%d)', [Handle])); 52 | end; 53 | end; 54 | 55 | destructor TMyClass.Destroy; 56 | begin 57 | FTimerQueue.Free; 58 | inherited; 59 | end; 60 | 61 | procedure TMyClass.OnTimer(const ASender: TObject); 62 | var 63 | Timer: TgoTimer; 64 | begin 65 | Timer := ASender as TgoTimer; 66 | // each timer callback event with unique handle and threadid 67 | Log(Format('OnTimer (Handle=%d, ThreadId=%d)', [Timer.Handle, GetCurrentThreadId])); 68 | end; 69 | 70 | begin 71 | try 72 | MyClass := TMyClass.Create; 73 | try 74 | Readln; // wait 75 | finally 76 | MyClass.Free; 77 | end; 78 | except 79 | on E: Exception do 80 | Writeln(E.ClassName, ': ', E.Message); 81 | end; 82 | end. 83 | -------------------------------------------------------------------------------- /Example/DelphiTimerQueue.deployproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | DelphiTimerQueue\ 18 | DelphiTimerQueue.exe 19 | ProjectOutput 20 | 0 21 | 22 | 23 | True 24 | True 25 | 26 | 27 | 28 | 29 | DelphiTimerQueue\ 30 | DelphiTimerQueue 31 | ProjectOutput 32 | 1 33 | 34 | 35 | True 36 | True 37 | 38 | 39 | 40 | 41 | DelphiTimerQueue.app\Contents\MacOS\ 42 | libcgunwind.1.0.dylib 43 | DependencyModule 44 | 1 45 | 46 | 47 | True 48 | 49 | 50 | DelphiTimerQueue.app\Contents\MacOS\ 51 | libcgsqlite3.dylib 52 | DependencyModule 53 | 1 54 | 55 | 56 | True 57 | 58 | 59 | 60 | 61 | 62 | DelphiTimerQueue.app\ 63 | libcgunwind.1.0.dylib 64 | DependencyModule 65 | 1 66 | 67 | 68 | True 69 | 70 | 71 | DelphiTimerQueue.app\ 72 | libPCRE.dylib 73 | DependencyModule 74 | 1 75 | 76 | 77 | True 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cross-platform timer queues for Windows and Linux 2 | In this article we will show how to use timer queues to create fast, lightweight, multi-threaded OnTimer() events that work on Windows and Linux in a uniform method using our helper class TgoTimerQueue. We also discuss how they operate on Windows and Linux and show an example application using timer queues. 3 | 4 | For more information about us, our support and services visit the [Grijjy homepage](http://www.grijjy.com) or the [Grijjy developers blog](http://blog.grijjy.com). 5 | 6 | The example contained here depends upon part of our [Grijjy Foundation library](https://github.com/grijjy/GrijjyFoundation). 7 | 8 | The source code and related example repository is hosted on GitHub at [https://github.com/grijjy/DelphiTimerQueue](https://github.com/grijjy/DelphiTimerQueue). 9 | 10 | ## What is a timer queue? 11 | You are probably already familiar with Delphi timer objects. You set an interval and your OnTimer() event is called at the given interval. Timer queues are a bit different. They provide a lightweight object to handle numerous timers that fire at different intervals. These lightweight objects are handled from a thread pool that is managed by the operating system so that multiple timers can be handled by a single thread. 12 | 13 | If your callback event executes fast enough so that it take less time than the internal rate of the timer, then it is possible for the operating system to use only a single thread to call your OnTimer() events. However the operating system handles the issues related to making sure that more threads are used if other OnTimer events must be called. 14 | 15 | Timer queues tend to be much more precise in their interval rate and scale up more efficiently than traditional timers. Since they are operating from a thread, you have to make sure anything you do within the event itself is thread-safe. With traditional Delphi timers your OnTimer() events are happening in the main application thread so this is not an issue. However, this may be a problem for existing code that is not thread-safe or a may be a benefit if you need your timer events to happen in the background. 16 | 17 | ## Windows CreateTimerQueueTimer 18 | On Windows we have a few APIs related to timer queues including `CreateTimerQueue`, `CreateTimerQueueTimer`, `ChangeTimerQueueTimer` and `DeleteTimerQueueTimer`. These APIs allow you to define a queue to manage the timers and create individual handles to timer objects. With each given handle you specify an interval rate and a callback procedure. 19 | 20 | To create a Windows timer queue: 21 | ```Delphi 22 | TimerQueueHandle := CreateTimerQueue; 23 | ``` 24 | 25 | To destroy a Windows timer queue: 26 | ```Delphi 27 | DeleteTimerQueueEx(TimerQueueHandle, INVALID_HANDLE_VALUE); 28 | ``` 29 | 30 | To create a timer and add it to the queue: 31 | ```Delphi 32 | if CreateTimerQueueTimer(Handle, TimerQueueHandle, @WaitOrTimerCallback, MyObject, 0, Interval, 0) then 33 | begin 34 | // success 35 | end 36 | ``` 37 | In the above example, `Handle` is an `out` parameter that contains the resulting handle of the timer object once it is created. `TimerQueueHandle` is the primary handle for the timer queue. `WaitOrTimerCallback` is your callback procedure that is called for every timer event. 38 | 39 | The API allows you to specify your own user data, so in this case we provide our own MyObject that we will retrieve in the callback event. You can simply pass a Delphi TObject to `CreateTimerQueueTimer()` and use it directly by defining it as a parameter in the callback to `WaitOrTimerCallback`. 40 | 41 | ```Delphi 42 | procedure WaitOrTimerCallback(MyObject: TMyObject; TimerOrWaitFired: ByteBool); stdcall; 43 | begin 44 | if TimerOrWaitFired then 45 | begin 46 | // do something 47 | end; 48 | end; 49 | ``` 50 | The `stdcall` procedure will be called for each interval for each and every timer in the queue. In other words, you can expect this event to be called by multiple threads and it needs to be completely thread safe. 51 | 52 | In our example code we use MyObject to actually call an OnTimer() event that is part of MyObject. 53 | 54 | To delete a timer from the queue: 55 | ```Delphi 56 | if DeleteTimerQueueTimer(TimerQueueHandle, Handle, INVALID_HANDLE_VALUE) then 57 | ATimer.Free; 58 | ``` 59 | > The DeleteTimerQueueTimer method will block until all the pending callbacks for this specific timer object are completed. 60 | 61 | ## Linux timerfd_create 62 | 63 | Linux offers a special set of APIs including `timerfd_creat`e and `timerfd_settime` that allow you to define descriptors for timer objects. These are used in conjunction with the EPoll APIs to manage a queue of lightweight timer objects from a thread pool. 64 | 65 | Just like Windows timer queues, multiple timer objects can be handled by the same thread or different threads allowing your application using timer objects to scale up more efficiently. 66 | 67 | To create a Linux timer queue: 68 | ```Delphi 69 | TimerQueueHandle := epoll_create(IGNORED); 70 | ``` 71 | Since EPoll manages the queue for us, we create an EPoll handle. 72 | 73 | To destroy a Linux timer queue: 74 | ```Delphi 75 | __close(TimerQueueHandle); 76 | ``` 77 | 78 | To create a timer and add it to the queue: 79 | ```Delphi 80 | Handle := timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); 81 | Event.data.ptr := MyObject; 82 | Event.events := EPOLLIN or EPOLLET; 83 | if epoll_ctl(TimerQueueHandle, EPOLL_CTL_ADD, Handle, @Event) <> -1 then 84 | begin 85 | // success 86 | end; 87 | ``` 88 | In the above example we create a timer object by calling timerfd_create requesting a MONOTONIC timer, a timer that isn't impacted by system clock changes, and TFD_NONBLOCK for a non-blocking event timer. Then we add the timer to the queue managed by EPoll by calling `epoll_ctl`. Our `MyObject` is assigned to the `Event` data.ptr parameter so we can access it during the callback just like we do on Windows. 89 | 90 | To delete a timer from the queue: 91 | ```Delphi 92 | epoll_ctl(TimerQueueHandle.Handle, EPOLL_CTL_DEL, Handle, @Event); 93 | ``` 94 | We simply call epoll_ctl again with the handle to delete. 95 | 96 | To access the timerfd related APIs we added a new import header unit called Linuxapi.Timerfd that is part of the [Grijjy Foundation library](https://github.com/grijjy/GrijjyFoundation). 97 | 98 | ## Linux epoll_wait event loop 99 | Unlike Windows that directly calls back into your procedure when an internal is reached, on Linux you need to create an event loop that waits for the timer interval to be reached using `epoll_wait`. You perform this inside one or more worker threads. 100 | 101 | Each of the threads will wait for a timer interval, but only one of the threads will handle the event. This allows Epoll to load balance multiple timer events across a thread pool. 102 | 103 | ```Delphi 104 | procedure TTimerQueueWorker.Execute; 105 | var 106 | NumberOfEvents: Integer; 107 | I: Integer; 108 | Event: epoll_event; 109 | TotalTimeouts: Int64; 110 | Timer: TgoTimer; 111 | Error: Integer; 112 | begin 113 | while not Terminated do 114 | begin 115 | NumberOfEvents := epoll_wait(FOwner.Handle, @FEvents, MAX_EVENTS, 100); 116 | if NumberOfEvents = 0 then { timeout } 117 | Continue 118 | else 119 | if NumberOfEvents = -1 then { error } 120 | begin 121 | Error := errno; 122 | if Error = EINTR then 123 | Continue 124 | else 125 | Break; 126 | end; 127 | for I := 0 to NumberOfEvents - 1 do 128 | begin 129 | Timer := FEvents[I].data.ptr; 130 | if (FEvents[I].events AND EPOLLIN) = EPOLLIN then 131 | begin 132 | if __read(Timer.Handle, @TotalTimeouts, SizeOf(TotalTimeouts)) >= 0 then 133 | begin 134 | end; 135 | end; 136 | end; 137 | end; 138 | end; 139 | ``` 140 | In the above we still call the `__read()` against the timer event. This clears the timer event from the queue. 141 | 142 | ## Putting it all together 143 | To make it easy to use, we created the TgoTimerQueue class that operates on both Windows and Linux in a uniform manner. 144 | 145 | You simple create a timer queue: 146 | ```Delphi 147 | TimerQueue := TgoTimerQueue.Create; 148 | ``` 149 | 150 | Add add one or more timers to the queue: 151 | ```Delphi 152 | MyHandle := TimerQueue.Add(1000, OnTimer); 153 | ``` 154 | Here we add a timer than fires every 1000ms and calls your OnTimer() procedure. 155 | 156 | Your OnTimer() event is similar to a standard OnTimer() event in Delphi: 157 | ```Delphi 158 | procedure TMyClass.OnTimer(const ASender: TObject); 159 | var 160 | Timer: TgoTimer; 161 | begin 162 | Timer := ASender as TgoTimer; 163 | // each timer callback event with unique handle and threadid 164 | Log(Format('OnTimer (Handle=%d, ThreadId=%d)', [Timer.Handle, GetCurrentThreadId])); 165 | end; 166 | ``` 167 | Each timer has a different handle. We also show the ThreadId so it is clear that that your OnTimer() event needs to be thread-safe. 168 | > Please note that in our example code we call Writeln() to the console. This is not reliable as the Writeln() console routine in Delphi is not thread-safe. Making the console thread-safe is beyond the scope of this article. 169 | 170 | ## Example Application 171 | 172 | The example program for Linux and Windows is hosted on GitHub at [https://github.com/grijjy/DelphiTimerQueue](https://github.com/grijjy/DelphiTimerQueue). 173 | 174 | ## Conclusion 175 | We hope you find timer queues a useful addition to your application. They are certainly nice at solving some real world issues when you need precisely timed threaded callbacks. 176 | 177 | For more information about us, our support and services visit the [Grijjy homepage](http://www.grijjy.com) or the [Grijjy developers blog](http://blog.grijjy.com). 178 | 179 | The base classes described herein are part of our [Grijjy Foundation library](https://github.com/grijjy/GrijjyFoundation). -------------------------------------------------------------------------------- /Example/DelphiTimerQueue.dproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {07F979C6-B90A-48CD-B7A1-8B827BB70333} 4 | 18.2 5 | None 6 | DelphiTimerQueue.dpr 7 | True 8 | Debug 9 | Win32 10 | 129 11 | Console 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Base 34 | true 35 | 36 | 37 | true 38 | Base 39 | true 40 | 41 | 42 | true 43 | Base 44 | true 45 | 46 | 47 | true 48 | Base 49 | true 50 | 51 | 52 | true 53 | Base 54 | true 55 | 56 | 57 | true 58 | Base 59 | true 60 | 61 | 62 | true 63 | Cfg_1 64 | true 65 | true 66 | 67 | 68 | true 69 | Base 70 | true 71 | 72 | 73 | .\$(Platform)\$(Config) 74 | .\$(Platform)\$(Config) 75 | false 76 | false 77 | false 78 | false 79 | false 80 | RESTComponents;emsclientfiredac;FireDACIBDriver;emsclient;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;$(DCC_UsePackage) 81 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 82 | DelphiTimerQueue 83 | ..\..\GrijjyFoundation;$(DCC_UnitSearchPath) 84 | 85 | 86 | DBXSqliteDriver;DBXInterBaseDriver;tethering;DataSnapFireDAC;bindcompfmx;fmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;IndyIPCommon;bindcompdbx;IndyIPServer;IndySystem;fmxFireDAC;DbxCommonDriver;xmlrtl;DataSnapNativeClient;FireDACDSDriver;rtl;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage) 87 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png 88 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png 89 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png 90 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png 91 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png 92 | $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png 93 | $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png 94 | $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png 95 | $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png 96 | android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar 97 | 98 | 99 | DBXSqliteDriver;fmxase;DBXInterBaseDriver;tethering;DataSnapFireDAC;bindcompfmx;fmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;IndyIPCommon;bindcompdbx;IndyIPServer;IndySystem;fmxFireDAC;DbxCommonDriver;xmlrtl;DataSnapNativeClient;FireDACDSDriver;rtl;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage) 100 | 101 | 102 | DBXSqliteDriver;fmxase;DBXInterBaseDriver;tethering;DataSnapFireDAC;bindcompfmx;fmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;IndyIPCommon;bindcompdbx;IndyIPServer;IndySystem;fmxFireDAC;DbxCommonDriver;xmlrtl;DataSnapNativeClient;FireDACDSDriver;rtl;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage) 103 | 104 | 105 | DBXSqliteDriver;fmxase;DBXInterBaseDriver;tethering;DataSnapFireDAC;bindcompfmx;fmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;IndyIPCommon;bindcompdbx;IndyIPServer;IndySystem;fmxFireDAC;DbxCommonDriver;xmlrtl;DataSnapNativeClient;FireDACDSDriver;rtl;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage) 106 | 107 | 108 | FireDACADSDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;inetdb;emsedge;IndyCore;dsnap;DataSnapCommon;DataSnapConnectors;bindengine;FireDACOracleDriver;FireDACMySQLDriver;FireDACCommonODBC;DataSnapClient;IndySystem;FireDACDb2Driver;FireDACInfxDriver;emshosting;FireDACPgDriver;FireDACASADriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;rtl;DbxClientDriver;CustomIPTransport;bindcomp;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;dbrtl;IndyProtocols;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage) 109 | 110 | 111 | DBXSqliteDriver;fmxase;DBXInterBaseDriver;tethering;DataSnapFireDAC;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;emsedge;fmx;fmxdae;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;IndyIPCommon;bindcompdbx;IndyIPServer;IndySystem;fmxFireDAC;emshosting;FireDACPgDriver;FireDACASADriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;FireDACDSDriver;rtl;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage) 112 | true 113 | 114 | 115 | DBXSqliteDriver;fmxase;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;svnui;DataSnapFireDAC;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;DBXOracleDriver;inetdb;emsedge;fmx;fmxdae;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;IndyIPCommon;bindcompdbx;vcl;IndyIPServer;DBXSybaseASEDriver;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;emshosting;FireDACPgDriver;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;FireDACDSDriver;rtl;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage) 116 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 117 | Debug 118 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 119 | 1033 120 | true 121 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 122 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 123 | .\Bin 124 | (None) 125 | 126 | 127 | DBXSqliteDriver;fmxase;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;DataSnapFireDAC;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;DBXOracleDriver;inetdb;emsedge;fmx;fmxdae;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;IndyIPCommon;bindcompdbx;vcl;IndyIPServer;DBXSybaseASEDriver;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;emshosting;FireDACPgDriver;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;FireDACDSDriver;rtl;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage) 128 | true 129 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 130 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 131 | 132 | 133 | DEBUG;$(DCC_Define) 134 | true 135 | false 136 | true 137 | true 138 | true 139 | 140 | 141 | false 142 | 1033 143 | (None) 144 | 145 | 146 | false 147 | RELEASE;$(DCC_Define) 148 | 0 149 | 0 150 | 151 | 152 | 153 | MainSource 154 | 155 | 156 | Cfg_2 157 | Base 158 | 159 | 160 | Base 161 | 162 | 163 | Cfg_1 164 | Base 165 | 166 | 167 | 168 | Delphi.Personality.12 169 | Application 170 | 171 | 172 | 173 | DelphiTimerQueue.dpr 174 | 175 | 176 | Microsoft Office 2000 Sample Automation Server Wrapper Components 177 | Microsoft Office XP Sample Automation Server Wrapper Components 178 | 179 | 180 | 181 | 182 | 183 | DelphiTimerQueue 184 | true 185 | 186 | 187 | 188 | 189 | true 190 | 191 | 192 | 193 | 194 | true 195 | 196 | 197 | 198 | 199 | true 200 | 201 | 202 | 203 | 204 | true 205 | 206 | 207 | 208 | 209 | DelphiTimerQueue.exe 210 | true 211 | 212 | 213 | 214 | 215 | 1 216 | 217 | 218 | Contents\MacOS 219 | 1 220 | 221 | 222 | Contents\MacOS 223 | 0 224 | 225 | 226 | 227 | 228 | classes 229 | 1 230 | 231 | 232 | 233 | 234 | library\lib\armeabi-v7a 235 | 1 236 | 237 | 238 | 239 | 240 | library\lib\armeabi 241 | 1 242 | 243 | 244 | 245 | 246 | library\lib\mips 247 | 1 248 | 249 | 250 | 251 | 252 | library\lib\armeabi-v7a 253 | 1 254 | 255 | 256 | 257 | 258 | res\drawable 259 | 1 260 | 261 | 262 | 263 | 264 | res\values 265 | 1 266 | 267 | 268 | 269 | 270 | res\drawable 271 | 1 272 | 273 | 274 | 275 | 276 | res\drawable-xxhdpi 277 | 1 278 | 279 | 280 | 281 | 282 | res\drawable-ldpi 283 | 1 284 | 285 | 286 | 287 | 288 | res\drawable-mdpi 289 | 1 290 | 291 | 292 | 293 | 294 | res\drawable-hdpi 295 | 1 296 | 297 | 298 | 299 | 300 | res\drawable-xhdpi 301 | 1 302 | 303 | 304 | 305 | 306 | res\drawable-small 307 | 1 308 | 309 | 310 | 311 | 312 | res\drawable-normal 313 | 1 314 | 315 | 316 | 317 | 318 | res\drawable-large 319 | 1 320 | 321 | 322 | 323 | 324 | res\drawable-xlarge 325 | 1 326 | 327 | 328 | 329 | 330 | 1 331 | 332 | 333 | Contents\MacOS 334 | 1 335 | 336 | 337 | 0 338 | 339 | 340 | 341 | 342 | Contents\MacOS 343 | 1 344 | .framework 345 | 346 | 347 | 0 348 | 349 | 350 | 351 | 352 | 1 353 | .dylib 354 | 355 | 356 | 1 357 | .dylib 358 | 359 | 360 | 1 361 | .dylib 362 | 363 | 364 | Contents\MacOS 365 | 1 366 | .dylib 367 | 368 | 369 | 0 370 | .dll;.bpl 371 | 372 | 373 | 374 | 375 | 1 376 | .dylib 377 | 378 | 379 | 1 380 | .dylib 381 | 382 | 383 | 1 384 | .dylib 385 | 386 | 387 | Contents\MacOS 388 | 1 389 | .dylib 390 | 391 | 392 | 0 393 | .bpl 394 | 395 | 396 | 397 | 398 | 0 399 | 400 | 401 | 0 402 | 403 | 404 | 0 405 | 406 | 407 | 0 408 | 409 | 410 | Contents\Resources\StartUp\ 411 | 0 412 | 413 | 414 | 0 415 | 416 | 417 | 418 | 419 | 1 420 | 421 | 422 | 1 423 | 424 | 425 | 1 426 | 427 | 428 | 429 | 430 | 1 431 | 432 | 433 | 1 434 | 435 | 436 | 1 437 | 438 | 439 | 440 | 441 | 1 442 | 443 | 444 | 1 445 | 446 | 447 | 1 448 | 449 | 450 | 451 | 452 | 1 453 | 454 | 455 | 1 456 | 457 | 458 | 1 459 | 460 | 461 | 462 | 463 | 1 464 | 465 | 466 | 1 467 | 468 | 469 | 1 470 | 471 | 472 | 473 | 474 | 1 475 | 476 | 477 | 1 478 | 479 | 480 | 1 481 | 482 | 483 | 484 | 485 | 1 486 | 487 | 488 | 1 489 | 490 | 491 | 1 492 | 493 | 494 | 495 | 496 | 1 497 | 498 | 499 | 500 | 501 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 502 | 1 503 | 504 | 505 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 506 | 1 507 | 508 | 509 | 510 | 511 | 1 512 | 513 | 514 | 1 515 | 516 | 517 | 518 | 519 | ..\ 520 | 1 521 | 522 | 523 | ..\ 524 | 1 525 | 526 | 527 | 528 | 529 | 1 530 | 531 | 532 | 1 533 | 534 | 535 | 1 536 | 537 | 538 | 539 | 540 | 1 541 | 542 | 543 | 1 544 | 545 | 546 | 1 547 | 548 | 549 | 550 | 551 | ..\ 552 | 1 553 | 554 | 555 | 556 | 557 | Contents 558 | 1 559 | 560 | 561 | 562 | 563 | Contents\Resources 564 | 1 565 | 566 | 567 | 568 | 569 | library\lib\armeabi-v7a 570 | 1 571 | 572 | 573 | 1 574 | 575 | 576 | 1 577 | 578 | 579 | 1 580 | 581 | 582 | 1 583 | 584 | 585 | Contents\MacOS 586 | 1 587 | 588 | 589 | 0 590 | 591 | 592 | 593 | 594 | 1 595 | 596 | 597 | 1 598 | 599 | 600 | 601 | 602 | Assets 603 | 1 604 | 605 | 606 | Assets 607 | 1 608 | 609 | 610 | 611 | 612 | Assets 613 | 1 614 | 615 | 616 | Assets 617 | 1 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | False 631 | False 632 | False 633 | False 634 | True 635 | False 636 | True 637 | False 638 | 639 | 640 | 12 641 | 642 | 643 | 644 | 645 | 646 | --------------------------------------------------------------------------------