My experience using RemObjects PascalScript to run scripts in Delphi |
||
So, to make things working, I had to solve the following tasks:
Install the necessary componentsThe sources for PascalScript are free and can be downloaded on GitHub First, we need to compile and install the PascalScript Core library. For my Delphi 10.3, I used PascalScript_Core_D26.dproj. The process produced numerous compilation warnings. Most of them were concerned with non-Unicode functions. This is annoying, but in fact, PascalScript was initially developed before Delphi 2009 and Unicode support. There was a great desire to fix them all. But, let us go ahead and run our first script.
Run a test scriptThe most common task for a script in our program is to open, modify, and write text files. Therefore, I decided to test a new script engine using the following script:
Here, I should make a small clarification: DCScript supported many scripting languages, including VBScript, JavaScript, and PascalScript. Most of our scripts are developed in VBScript. However, due to difficulties with choosing an acceptable engine, VBScript will not be supported in the nearest future. Therefore, most likely, we will have to convert all our scripts to PascalScript or JavaScript. I will write about JavaScript implementation in the next article. To be simple, let us put a button and the following two components onto a form: TPSScript and TPSImport_Classes. The first one compiles and runs the script, and the last one imports VCL objects and structures from the Classes module into the script. PascalScript consists of two independent parts: uPSCompiler.pas and uPSRuntime.pas. The TPSScript component combines both these parts, compiles, and runs the script. You can do that using the Compile and the Execute methods, accordingly. The script compiler provides detailed information about errors, including the line number, and the position in the line of the script that caused the problem. You can find it within the CompilerMessages array property. Let us collect all error messages as a simple string.
Wow, the script runs, and even creates a file on the disk.
Import own class into the scriptThe original application actively exchanges data with scripts being running. In addition, it uses its own Delphi classes that must be available within the script. Let us implement a simple class, TJobParam, and import this class to the PascalScript engine.
Good guys from RemObjects have made a special utility for importing classes, which will be used in a script. This utility is also available on GitHub However, attempts to compile revealed several problems. This program requires a third-party SynEdit library, which is not supported by the latest version of Delphi. I found packages for XE5, and lower. Therefore, I decided to compile the program on my old Delphi 2007 I have.
Finally, we got a Delphi unit, uPSI_JobParams.pas, which provides a special plugin class with a proxy code for our TJobParam, similar to TPSImport_Classes. Now, we can create an instance of the TJobParam class and save it to a private field, which I declared within the MainForm class. You need to handle both the TPSScript.OnCompile and the TPSScript.OnExecute events to register this field in the script engine. The OnCompile event handler calls AddRegisteredPTRVariable for registering the used type. The other handler, OnExecute, passes a pointer to this field into the script. Please check out the following article to learn more about using classes with PascalScript
There are two ways of using the plugin class from uPSI_JobParams.pas. You can install TPSImport_JobParams as a component, and put it onto the form, in the same way as TPSImport_Classes. The other way is to create plugins at runtime. The last one is preferable because the original program runs scripts in threads. No forms are used. So, I created the imported plugin at runtime, removed the TPSImport_Classes instance from MainForm, and created it at runtime, as well.
Run scripts in threadsThe fast-and-easy way to check if the script engine works in threads is to run it in an anonymous thread. Now, Delphi provides a nice function - TThread.CreateAnonymousThread.
However, the original program utilizes a TThread descendant to run scripts. Therefore, I changed my code as follows:
Here, both the ScriptCompile and the ScriptExecute methods are event handlers for the TPSScript.OnCompile and the TPSScript.OnExecute events, accordingly. The Execute method contains code, which runs the script (see the previous code listing).
Pass parameters from Delphi to the script, and backTo exchange data with a script, which runs in a thread, I created an instance of TJobParam and passed it to the TScriptThread.Constructor. The mentioned event handlers (ScriptCompile and ScriptExecute) do all the work.
ConclusionThe implemented test program showed good results. You can download the Sources on GitHub The selected script engine can perform all tasks we need and can work in threads. We have already incorporated this solution into our original app - TaskRunner. Recently, we have open-sourced it - TaskRunner on GitHub The program still has some legacy user interface, but we are working on this task. TaskRunner serves to automate the Software build tasks, deploying builds, and can also be used for many other tasks. You are free to clone or fork this repository. Your opinion, suggestions, and improvements will be greatly appreciated.
Nikita Shirokov
|