1) Today, I unlock the feature 'Runtime Scripts' for FMXInno for general use.
So, what are '
Runtime Scripts'? They are scripts that run at runtime without needing to be compiled at compile-time. This means that once you compile your Setup, you don't need to recompile it to apply changes made in '
Runtime Scripts'.
Enough explanations, let's dive into a simple example:
Here, I use the file extension '
.fxs' for Runtime Scripts, but you can use whatever you want: '
.pas', '
.iss', '
.txt', etc.
Code:
Test_1.fxs
-------------------------------------------
program Test1CreateRect; // This line is optional, so you can get rid of it.
var ARect: FRectangle;
procedure CleanUpRect;
begin
ARect := nil;
end;
begin
ARect := InitRectangleHandle;
ARect.FCreate(FMXFormHandle);
ARect.SetBounds(200, 90, 250, 250);
ARect.FillColor(ALRed);
AExternalObj := ARect.Handle;
end.
Code:
Example.iss
-------------------------------------------
var
ARuntimeScript: FRuntimeScript;
FirstRun : Boolean;
AExternalObj: TFMXObject;
procedure ABtnOnClick(Sender: TObject);
begin
if not FirstRun then
begin
// Get the handle of the external object that was created with the external script.
AExternalObj := ARuntimeScript.GetRegisteredLongIntVar('AExternalObj');
// Call for cleanup.
ARuntimeScript.CallProcFromScriptNoVar('CleanUpRect');
// Remove the external object from FMXForm as well.
FMXForm.RemoveObject(AExternalObj);
end;
ARuntimeScript.CompileAndRun;
FirstRun := False;
end;
Code:
procedure InitializeWizard();
begin
{ FMX Form }
FMXForm.FCreateFluent(WizardForm.Handle, False, False, 0.96, 0);
{ ABtn }
ABtn.FCreate(FMXForm.Handle);
ABtn.Text('&Run');
ABtn.OnClick(@ABtnOnClick);
{ ARuntimeScript }
ARuntimeScript.FCreate(ExpandConstant('{src}\_Scripts\Test_1.fxs'));
ARuntimeScript.RegisterLongIntConst('FMXFormHandle', FMXForm.Handle);
ARuntimeScript.RegisterLongIntVar('AExternalObj', 0);
end;
1. This example will create a Rectangle (
ARect1: FRectangle) at runtime, while the Setup is running.
2. And make some changes to the Runtime Script and smash the '
Run' button to see the changes.
Code:
// ARect.FillColor(ALRed);
ARect.FillColor(ALGreen);
3. Try creating a similar object (
ARect2) by following the '
ARect1' code. Once you save the file, click the '
Run' button.
Code:
Test_1.fxs
-------------------------------------------
var ARect, ARect2: FRectangle;
begin
ARect := InitRectangleHandle;
ARect.FCreate(FMXFormHandle);
ARect.SetBounds(80, 90, 200, 200);
ARect.FillColor(ALRed);
ARect2 := InitRectangleHandle;
ARect2.FCreate(FMXFormHandle);
ARect2.SetBounds(360, 90, 200, 200);
ARect2.FillColor(ALBlue);
...
end.
* FMXInno has supported runtime scripts for quite a while with '
Lua'. Some may not be familiar with '
Lua', so i didn't promote it. On the other hand, '
FRuntimeScript' is Pascal, so everyone who uses InnoSetup can use this without any doubt.
[*] Built-In Features / Functions / Constants / Variables:
- 'Runtime Scripts' has built-in support for FMXInno classes, so you can use them inside 'Runtime Scripts'.
-- Currently, it only supports the 'FMX_Standard', 'FMX_Shapes', 'FMX_Additional', 'FMX_Layouts', and 'FMX_Effects' classes. because i'm too lazy to add all the classes at the moment.
- 'Runtime Scripts' are not literally InnoSetup, so functions like "ExpandConstant" and "ExtractTemporaryFile" used in InnoSetup can't be used in runtime scripts.
-- However, I've added some extra useful functions by default from 'System.SysUtils', 'System.Math', 'Winapi.Windows', etc., and you'll find them in the 'BuiltIn Features.txt' file.
- Added default variable 'Application: TApplication', so you can now use 'Application.ProcessMessages;' inside the runtime scripts.
[*] Register Custom Types:
Code:
Example.iss
-------------------------------------------
ARuntimeScript.RegisterType('TDummyRec', 'record' +#13#10+
' PosX: Integer;' +#13#10+
' PosY: Integer;' +#13#10+
'end;');
ARuntimeScript.RegisterType('TsNum', 'Single');
Test_1.fxs
-------------------------------------------
var
ShippingFees: TsNum;
ADummyRec: TDummyRec;
[*] Load External Libraries (DLLs):
You can call external DLLs inside runtime scripts in three different ways,
-- Supported flags: '
stdcall', '
cdecl', and '
delayload'.
1. System Libs:
Code:
Test_1.fxs
-------------------------------------------
function KillTimer(handle: HWND; TimerId: LongWord): boolean;
external 'KillTimer@user32.dll stdcall';
2. Libs from custom path:
Code:
Test_1.fxs
-------------------------------------------
procedure MyDllFunc(hWnd: Integer);
external 'MyDllFunc@"C:\Test\Libs\MyDll.dll" stdcall';
3. I've added three special constants to runtime scripts, but you need to define them manually first.
(a) 'Src':
Code:
Example.iss
-------------------------------------------
ARuntimeScript.RegisterSpecialSrcConst(ExpandConstant('{src}'));
Test_1.fxs
-------------------------------------------
procedure MyDllFunc(hWnd: Integer);
external 'MyDllFunc@"{src}\MyDll.dll" stdcall';
(b) 'App':
Code:
Example.iss
-------------------------------------------
ARuntimeScript.RegisterSpecialAppConst(ExpandConstant('{src}\_ExtrnlDll'));
Test_1.fxs
-------------------------------------------
procedure MyDllFunc(hWnd: Integer);
external 'MyDllFunc@"{app}\MyDll.dll" stdcall';
(c) 'Tmp':
Code:
Example.iss
-------------------------------------------
ARuntimeScript.RegisterSpecialTmpConst(ExpandConstant('{src}\_ExtrnlDll'));
Test_1.fxs
-------------------------------------------
procedure MyDllFunc(hWnd: Integer);
external 'MyDllFunc@"{tmp}\MyDll.dll" stdcall';
(d) You can also use 'files:' to define the path.
Code:
Test_1.fxs
-------------------------------------------
procedure MyDllFunc(hWnd: Integer);
external 'MyDllFunc@"files:MyDll.dll" stdcall';
(i) In runtime scripts, 'files:' is equal to 'Tmp'.
(ii) It doesn't extract the Lib to the 'Tmp' directory as InnoSetup does.
(e) Added built-in support for 'CreateCallback' function.
Code:
Test_1.fxs
-------------------------------------------
SetTimer(0, 0, 250, CreateCallback(@TimerProc1));
[*] Call a function/procedure declared in Runtime Scripts through InnoSetup:
There are two ways to call a function/procedure declared in Runtime Scripts via InnoSetup.
(1) Without Parameters:
Code:
Test_1.fxs
-------------------------------------------
procedure SayHi;
begin
ShowMessage('Hi, I am from the Runtime Script!');
end;
Example.iss
-------------------------------------------
ARuntimeScript.CallProcFromScriptNoVar('SayHi');
(2) With Parameters:
-- Here you have to use FNewArray for Parameters.
Code:
Test_1.fxs
-------------------------------------------
procedure CalAddition(const x, y: Integer);
begin
MyMathResult := x + y;
end;
Example.iss
-------------------------------------------
var
AParams : FNewArray;
{ AParams }
AParams.FCreate;
AParams.AddInteger(32);
AParams.AddInteger(37);
ARuntimeScript.CallProcFromScript('CalAddition', AParams);
[*] Register InnoSetup's function/procedure and use them in Runtime Script:
You can register functions/procedures for Runtime Scripts that have been declared in the InnoSetup (.iss).
1. At the moment, it doesn't accept any parameters.
2. You have to use the Callback function.
Code:
Example.iss
-------------------------------------------
type
TDummyFunc2 = function: Integer;
function XWrapNoVarFuncNew(Callback: TDummyFunc2): Longword;
external 'XWrapNoVar@files:FMXInno.dll stdcall delayload';
function GetValueFromInnoSetup: Integer;
begin
Result := 69;
end;
procedure InnoMsgProc;
var
LNum: Longint;
begin
MsgBox('Hi', mbInformation, MB_OK);
end;
procedure InitializeWizard();
begin
...
ARuntimeScript.RegisterProcNoVar(XWrapNoVarProc(@InnoMsgProc), 'procedure InnoMsgProc;');
ARuntimeScript.RegisterProcNoVar(XWrapNoVarFuncNew(@GetValueFromInnoSetup), 'function GetValueFromInnoSetup: Integer;');
...
end;
Code:
Test_1.fxs
-------------------------------------------
program Test7RegisterProc;
var LuckyNum: Integer;
procedure DoIt;
begin
InnoMsgProc();
end;
begin
LuckyNum := GetValueFromInnoSetup();
DoIt;
end.
[*] PreProcessor and Include Files:
Runtime scripts support preprocessors and include files.
Code:
Test_1.fxs
-------------------------------------------
program Test10aIncludeFiles; // This line is optional, so you can get rid of it.
{$DEFINE WHO_ARE_YOU}
{$I ".\_Scripts\Test_10b.fxs"}
var S: String;
begin
{$IFDEF WHO_ARE_YOU}
S := 'Mr. BLACKFIRE';
{$ELSE}
S := 'Mr/Ms/Mrs. Nobody';
{$ENDIF}
SayHi(S);
end.
[*] Syntax Highlighting in the Text Editor:
Open the '
.fxs' file in your favorite text editor or IDE and select the file syntax as '
Pascal'. Now you're good to go.
.