Get strings from ini file and use multiple times - inno-setup

I want to store text from ini file into a variable and use the var many times along my setup pages,
For instace lets say I have ini file named-"datatxt", ini section -[txtdata], section key-"htxt", key value - "hello world".
I want to store this text value in a var named - "hiVar" and use it here-
Title :=
TNewStaticText.Create(MOPage);
Title.Parent := MOPage.Surface;
Title.Font.Name := 'Verdana';
Title.Caption := hiVar;

For declaring variables there are two scopes available. Local and global. Locally declared variables are visible only within the body of a procedure or method where they are declared. They are widely used as a temporary storage for intermediate operations, or for holding object references (as you already do):
procedure DoSomething;
var
S: string; // <- this is a locally declared variable
begin
// only inside this procedure the S variable can be accessed
end;
Globally declared variables (which is your question about) are visible in the scope of all procedures and methods within the whole code scripting section. They are used for holding references of objects whose are used across script code, passing results of some operations between event methods, or for holding some permanent values (which is your case):
var
S: string; // <- this is a globally declared variable
procedure DoSomething;
begin
// inside this procedure the S variable can be accessed
end;
procedure DoSomethingElse;
begin
// as well as inside this procedure the S variable can be accessed
end;
Answer your question with an example is quite hard, since you haven't desribed the context in which you want to read that INI file, so it's hard to tell in which event you should read it. In the following example the INI file value is read when the wizard form is initialized. You can see there an access to a global variable from another method as well:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Files]
Source: "datatxt"; Flags: dontcopy
[Code]
var
hiVar: string; // <- this is a globally declared variable
procedure InitializeWizard;
begin
// extract the file into the setup temporary folder
ExtractTemporaryFile('datatxt');
// assign the read value into a global variable
hiVar := GetIniString('txtdata', 'htxt', '', ExpandConstant('{tmp}\datatxt'));
// from now on the variable should contain the key value
end;
procedure CurPageChanged(CurPageID: Integer);
begin
// just a random code showing that you can access global
// variables across the methods
if CurPageID = wpWelcome then
MsgBox(hiVar, mbInformation, MB_OK);
end;

Related

How do you determine if an object has be constructed in Inno Setup Pascal Script?

How do I check that my checkbox has been created / constructed and can be used to check if checked?
[Code]
var
MyCheckBoxThatMayExistOrNot: TNewCheckBox;
procedure Whatever();
begin
{ Check if MyCheckBoxThatMayExistOrNot exists and checked }
if ????? and MyCheckBoxThatMayExistOrNot.Checked then
begin
...
end;
end;
TIA!!
Compare the variable value against nil:
if (MyCheckBoxThatMayExistOrNot <> nil) and
MyCheckBoxThatMayExistOrNot.Checked then
An equivalent is use of Assigned function:
if Assigned(MyCheckBoxThatMayExistOrNot) and
MyCheckBoxThatMayExistOrNot.Checked then
You might want to explicitly initialize the variable to nil in InitializeSetup or InitializeWizard, but it should not be necessary: Are global variables in Pascal Script zero-initialized?

How to use Pipeline pattern in Delphi

I am trying to implement a Pipeline pattern in my test project (How to make a Mutlithreded idhttp calls to do work on a StringList), but am having a struggle adapting TThread code to Pipeline pattern code. There are not many resources about how to use it.
I tried my best below, please DO NOT downvote, I know my code is messy but I'll edit my question if needed.
type
TForm2 = class(TForm)
...
private
procedure Retriever(const input: TOmniValue; var output: TOmniValue);
procedure Inserter(const input, output: IOmniBlockingCollection);
function HttpGet(url: string; var page: string): boolean;
end;
procedure TForm2.startButton1Click(Sender: TObject);
var
pipeline: IOmniPipeline;
i : Integer;
v : TOmniValue;
s : string;
urlList : TStringList;
begin
pipeline := Parallel.Pipeline;
pipeline.Stage(Retriever);
pipeline.Stage(Inserter).NumTasks(10);
pipeline.Run;
for s in urlList do
pipeline.Input.Add(s);
pipeline.Input.CompleteAdding;
// wait for pipeline to complete
pipeline.WaitFor(INFINITE);
end;
function TForm2.HttpGet(url: string; var page: string): boolean;
var
lHTTP: TIdHTTP;
i : integer;
X : Tstrings;
S,M,fPath : String;
begin
lHTTP := TIdHTTP.Create(nil);
X := TStringList.Create;
try
X.Text := lHTTP.Get('https://instagram.com/'+fPath);
S:= ExtractDelimitedString(X.Text);
X.Clear;
Memo2.Lines.Add(fPath+ ' : '+ M ); //how to pass the result to Inserter
finally
lHttp.Free;
end;
end;
procedure TForm2.Inserter(const input, output: IOmniBlockingCollection);
var
result : TOmniValue;
lpage : string;
begin
for result in input do begin
Memo2.Lines.Add(lpage);
FreeAndNil(lpage);
end;
// correect?
end;
procedure TForm2.Retriever(const input: TOmniValue; var output: TOmniValue);
var
pageContents: string;
begin
if HttpGet(input.AsString, pageContents) then
output := //???
end;
First of all - describe what is your specific problem. No one can stand behind your back and look at your computer and see what you are doing.
http://www.catb.org/esr/faqs/smart-questions.html#beprecise
You do imply your program misbehaves. But you do not describe how and why. And we do not know it.
As general remarks, you overuse the pipeline a bit.
all the worker procedures you pass to OTL - in your case those are Inserter and Retriever work in random threads. That means none of them should touch GUI without synchronizing - VCL is not multithreaded.
Also using TThread.Synchronize is a poor choice as I explained to you in the linked question. It makes program slow and it makes forms unreadable. To update your form use polling with fixed framerate. Do not update your form from inside OTL workers.
In other words, Inserter is not what you need. All you need from the pipeline here is its Input collection, a downloader procedure and the Output collection. Yes it is very simple task for the complex things pipelines are, that is why I mentioned two other simpler patterns before it.
You need TTimer on your form that would poll the Output collection at fixed framerate 2-3 times per second, and check that the collection is not finalized yet ( if it is - the pipeline got stopped ) and that should update GUI from a main thread.
You should not wait for a pipeline to finish inside your main VCL thread. Instead You should detach the pipeleine and let it run totally in background. Save the reference to the created pipeline into the Form's member variable so you could access its Output collection from the TTimer event and also can free the pipeline after its process run over.
You should keep that variable linked to the pipeline object until the downloading is over and set to nil (Free the objects) after that, but not before. You know about interfaces and reference-counting in Delphi, right?
For other OTL patterns like parallel-FOR read OTL docs about their .NoWait() calls.
You should make this Your form bi-modal, to have different set of enabled controls when downloading is running and when it is not. I usually do it with special Boolean property like I shown to you in the topic you linked.
Your user is not supposed to change the lists and settings while the pipeline is in progress (unless you would implement that realtime task changing, but you did not yet). This mode switcher would also be a good place to free the finished pipeline object when the switching is going from working mode to idle mode.
If you would want to play with the pipeline workers chaining, then you can put into the Input Collection not the URL strings themselves, but the array of those - the Memo1.Lines.ToArray(), then you can start with Unpacker stage that gets string arrays from the input collection (there would be only one, actually) and enumerate it and put the strings into stage-output collection.
This however has little practical value, it would even slow your program down a tiny bit, as the Memo1.Lines.ToArray() function would still work in the main VCL thread. But just to experiment with the pipelines this might be funny.
So the draft becomes like that,
TfrmMain = class(TForm)
private
var pipeline: IOmniPipeline;
property inProcess: Boolean read ... write SetInProcess;
...
end.
procedure Retriever(const input: TOmniValue; var output: TOmniValue);
var
pageContents, URL: string;
lHTTP: TIdHTTP;
begin
URL := input.AsString;
lHTTP := TIdHTTP.Create(nil);
try
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := True;
pageContents := ExtractDelimitedString( lHTTP.Get('https://instagram.com/' + URL) );
if pageContents > '' then
Output := pageContents;
finally
lHTTP.Destroy;
end;
end;
procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if InProgress then begin
CanClose := False;
ShowMessage( 'You cannot close this window now.'^M^J+
'Wait for downloads to complete first.' );
end;
end;
procedure TfrmMain.SetInProcess(const Value: Boolean);
begin
if Value = InProcess then exit; // form already is in this mode
FInProcess := Value;
memo1.ReadOnly := Value;
StartButton.Enabled := not Value;
if Value then
Memo2.Lines.Clear;
Timer1.Delay := 500; // twice per second
Timer1.Enabled := Value;
If not Value then // for future optimisation - make immediate mode change
FlushData; // when last worker thread quits, no waiting for timer event
If not Value then
pipeline := nil; // free the pipeline object
If not Value then
ShowMessage('Work complete');
end;
procedure TfrmMain.Timer1Timer(const Sender: TObject);
begin
If not InProcess then exit;
FlushData;
if Pipeline.Output.IsFinalized then
InProcess := False;
end;
procedure TForm2.startButton1Click(Sender: TObject);
var
s : string;
urlList : TStringList;
begin
urlList := Memo1.Lines;
pipeline := Parallel.Pipeline;
pipeline.Stage(Retriever).NumTasks(10).Run;
InProcess := True; // Lock the input data GUI - user no more can edit it
for s in urlList do
pipeline.Input.Add(s);
pipeline.Input.CompleteAdding;
end;
procedure TfrmMain.FlushData;
var v: TOmniValue;
begin
if pipeline = nil then exit;
if pipeline.Output = nil then exit;
if pipeline.Output.IsFinalized then
begin
InProcess := False;
exit;
end;
Memo2.Lines.BeginUpdate;
try
while pipeline.Output.TryTake(v) do
Memo2.Lines.Add( v.AsString );
finally
Memo2.Lines.EndUpdate;
end;
// optionally - scroll output memo2 to the last line
end;
Note few details, think about them and understand the essence of those:
Only FlushData is updating the output memo. FlushData is called from the TTimer event or from the form mode property setter. Both of them only are ever called from the main VCL thread. Thus FlushData is NEVER called form background threads.
Retriever is a free standalone function, it is not a member of the form and it knows nothing about the form and has no reference to your form instance(s). That way you achieve both goals: you avoid "tight coupling" and you avoid a chance of mistakingly access the form's controls from a background thread, which is not allowed in VCL.
Retriever functions work in background threads, they do load the data, they do store the data, but they never touch the GUI. That is the idea.
Rule of thumb - all methods of the form are only called from the main VCL thread. All pipeline stage subroutines - bodies of the background threads - are declared and work outside of any VCL forms and have no access to none of those. There should be no mix between those realms.
you throttle GUI update to a fixed refresh rate. And that rate should be not too frequent. Windows GUI and user eyes should have time to catch up.
Your form operates in two clearly delineated modes - InProcess and not InProcess. In those modes different sets of functions and controls are available to the user. It also manages mode-to-mode transitions like clearing output-memo text, alerting user of status changes, freeing memory of used threads-managing objects (here: pipelines), etc. Consequently, this property only is changed (setter is called) from main VCL thread, never from background workers. And #2 helps with that too.
The possible future enhancement would be to use pipeline.OnStop event to issue a PostMessage with a custom Windows Message to your form, so it would switch the mode immediately as the work is done, not waiting for the next timer olling event. This might be the ONLY place where pipeline knows anything about the form and has any references to it. But this open the can of Windows messaging, HWND recreation and other subtle things that I do not want to put here.

innosetup semicolon expected in code section

I have an error while compiling the code section of my inno script.
The code section
var
ServerID: String;
EditServerID: TEdit;
PageIDServer: TWizardPage;
function getServerID(Param: String): String;
begin
Result := ServerID.Text; <--- Error here
end;
And the procedure section:
if InstallService(ExpandConstant('"{app}\Client.exe -{code:GetServerID}" Client'),'Client','Client','Client',SERVICE_WIN32_OWN_PROCESS,SERVICE_AUTO_START) = true then
begin
StartService('Client');
Sleep(500);
end
else
MsgBox('Client service could not be installed',mbInformation, MB_OK);
I read that this error may be linked to the {code:} but don't know why.
Thanks for your help.
You were trying to access a Text property of a string variable ServerID, but you certainly wanted to get that value from the EditServerID edit box. If that is so, write it this way:
function GetServerID(Param: string): string;
begin
Result := EditServerID.Text;
end;
The same applies for the code in your NextButtonClick event method. Btw. the ServerID variable seems to be unused in your script, you're just assigning its value to the EditServerID.Text property when the edit box is created, but at that time the variable is empty, so I think you can just remove it from your script to not mislead you anymore.

Hook standard Inno Setup checkbox

I added an InputOptionWizardPage for selecting tasks. This works fine, but I would like to add some custom functionality. One task is dependent on the other, so if the second checkbox is checked, the first should be checked and grayed out.
To do this, I need to access the properties of a checkbox. I found ways to do this using a completely custom page, where I would explicitly create the checkbox myself, but that would be a lot of work, since most of what I have so far is satisfactory.
How can I hook a checkbox that was created by Inno Setup, using MyInputOptionWizardPage.Add('This will add a checkbox with this caption')?
In attempt to answer your question directly.
I suspect you have used CreateInputOptionPage() which returns a TInputOptionWizardPage
This has the '.Add('Example')` method that you mention.
TInputOptionWizard descends TWizardPage which descends from TComponent which has the methods you need.
Update: Replaced original Code, this example is based on a review of options available in the InnoSetup source code of ScriptClasses_C.pas My original example I thought
that TRadioButton and TCheckBox where individual controls. They instead its one control called TNewCheckListBox. There is a couple of ways someone could pull this off but the safest way is to use.
This example is a complete Inno Setup Script.
[Setup]
AppName='Test Date Script'
AppVerName='Test Date Script'
DefaultDirName={pf}\test
[Code]
const
cCheckBox = false;
cRadioButton = true;
var
Opt : TInputOptionWizardPage;
function BoolToStr(Value : Boolean) : String;
begin
if Value then
result := 'true'
else
result := 'false';
end;
procedure ClickEvent(Sender : TObject);
var
Msg : String;
I : Integer;
begin
// Click Event, allowing inspection of the Values.
Msg := 'The Following Items are Checked' +#10#13;
Msg := Msg + 'Values[0]=' + BoolToStr(Opt.Values[0]) +#10#13;
Msg := Msg + 'Values[1]=' + BoolToStr(Opt.Values[1]) +#10#13;
Msg := Msg + 'Values[2]=' + BoolToStr(Opt.Values[2]);
MsgBox(Msg,mbInformation,MB_OK);
end;
procedure InitializeWizard();
var
I : Integer;
ControlType : Boolean;
begin
ControlType := cCheckBox;
Opt := CreateInputOptionPage(1,'Caption','Desc','SubCaption',ControlType, false);
Opt.Add('Test1');
Opt.Add('Test2');
Opt.Add('Test3');
// Assign the Click Event.
Opt.CheckListBox.OnClickCheck := #ClickEvent;
end;
You can also control tasks by parent relationships, it gives you a similar behavior to what your asking for but is not 100% the same. I know this does not answer your question directly, but intends to give you an option that maybe easier to implement. Doing it this way you don't have to worry about managing a custom dialog at all.
[Setup]
;This allows you to show Lines showing parent / Child Relationships
ShowTasksTreeLines=yes
[Tasks]
;Parent Tasks don't use "\"
Name: p1; Description: P1 Test;
;Child Tasks are named ParentTaskName\ChildTaskName
;Flag don't inheritcheck:Specifies that the task
;should not automatically become checked when its parent is checked
Name: p1\c1; Description: C1 Test; Flags: dontinheritcheck;
Name: p1\c2; Description: C2 Test;
;Default behavior is that child must be selected
;when a parent is selected
;this can be overridden using the:
;doninheritcheck flag and the checkablealone flag.
Name: p2; Description: P2 Test; Flags: checkablealone;
Name: p2\c1; Description: P2-C1 Test; Flags: dontinheritcheck;
Name: p2\c2; Description: P2-C2 Test; Flags: dontinheritcheck;

Call a TDataModule method in TThread.Execute

In general, is it possible in a TThread.Execute procedure
to call a TDataModule method, in which there is no visual activity involved?
Thanks to all, Massimo.
The easiest way to go is to use TThread.Synchronize to invoke a method in your data module.
However, if you do not wish to do that, even when no visual activity is involved, you should determine whether or not you need to add a critical section to protect you.
Any access to any standard or third-party VCL component, whether it is visual (TButton) or non-visual (datasets) should be considered UNSAFE. Any access to a local data object (like a private field or global variable) must also be protected by critical sections.
Here's a direct call from a from background thread to your data module:
if Assigned(MyDataModule) then MyDataModule.DoSomething(a,b,c);
Here's the code in your data module, which I am showing you a sample bit of code that makes sure that we are the only thread touching FList right now:
/// DoSomething: Note this method must be thread-safe!
procedure TMyDataModule.DoSomething(a:TMyObject1;b:TMyObject2;c:TMyObject3);
begin
FCriticalSection.Enter;
try
if not FList.Contains(a) then
FList.Add(a);
...
finally
FCriticalSection.Leave;
end;
end;
/// elsewhere in the same data module, wherever anybody modifies or checks the state
/// (content) of FList, wrap the method with a critical section like this:
function TMyDataModule.HasItem(a:TMyObject1):Boolean;
begin
FCriticalSection.Enter;
try
result := FList.Contains(a);
finally
FCriticalSection.Leave;
end;
end;
Some starter rules for Delphi multi-threaded programming, in a nutshell are:
Don't do anything that could create a Race Condition.
Don't forget to use synchronization primitives like Critical Sections, Mutexes, etc, to protect against concurrency issues including Race Conditions, whenever you are accessing any data fields in your class (data module) or ANY globals. If you use these improperly you add deadlocks to your list of problems. So this is not a good place to mess up.
If you must access a VCL component or object in any way, do so indirectly via PostMessage, TThread.Synchronize, or some other thread-safe equivalent way of signaling the main thread that you need something done.
Think about what happens when you're shutting down. Maybe you could check if your data module even exists, since it might have gone away, before you invoke its methods.
Short answer: yes
Long answer: The problem with Windows is that all the GUI activity should be done in a single thread. (Well, the above statement can be expanded, amended, enhanced etc. but for our discussion is enough). So, if you are sure that in your TDataModule method there isn't any 'GUI thing' involved (beware, this can be even a ShowMessage call) then go ahead.
UPDATE: Of course, there are techniques to update your GUI from a secondary thread, but this implies some sort of preparation (message passing, Synchronize etc.). Isn't something very hard, just that you cannot 'blindly' call from another thread a method who changes the GUI.
To use our industries favorite answer when asked anything: It depends.
If you have a method on your datamodule that is completely self contained (ie could be a static method), you shouldn't have any problem.
Example
TMyDataModule = class(TDataModule)
public
function AddOne(const Value: Integer): Integer;
end;
function TMyDataModule.AddOne(const Value: Integer): Integer;
begin
Result := Value + 1;
end;
If on the other hand, the method uses any global state, you might get into trouble when calling it from multiple threads.
Example
TMyDataModule = class(TDataModule)
private
FNumber: Integer
public
function AddOne(const Value: Integer): Integer;
end;
function TMyDataModule.AddOne(const Value: Integer): Integer;
begin
FNumber := Value
//***** A context switch here will mess up the result of (at least) one thread.
Result := FNumber + 1;
end;
Global state should be interpreted very wide. A TQuery, a TTable, refreshing the GUI, using any global variable, ... is all global state and isn't thread safe.
Yes, my question is very vague.
My program is a graphical statistics app, it has to display Gantt chart, by means of TChart, describing the states, alarms or machined orders of one or more Tool Machine.
On the supervisor PC a server (equipped with a TIdTcpServer and some DB components)
is listening to my app on the LAN.
The main-form client allows the final user to choice a range of dates (period) and
the units (machines) to query the server. After that, the user press a button (there are
3 functionalities): a new form (and Datamodule) is created to display the results.
The work of collecting data is completed by a thread because:
1) it can be a long job so it could freeze the GUI;
2) the user can launch more than one form to see various results.
I have a basic Datamodule (with a TIdTcpClient with several function to collect the data),
a basic form (never instantiated, with a lot of characteristics common to all data form, and the definition of the worker thread).
unit dtmPDoxClientU;
TdtmPDoxClient = class(TDataModule)
IdTCPClient: TIdTCPClient;
...
function GetData(...): boolean;
...
end;
unit frmChartBaseFormU;
TfrmChartBaseForm = class(TForm)
...
TheThread: TThreadClient;
procedure WMThreadComm(var Message: TMessage); message WM_THREADCOMM;
procedure ListenThreadEvents(var Message: TMessage); virtual;
procedure ExecuteInThread(AThread: TThreadClient); virtual;
end;
TThreadClient = class(TThread)
private
public
Task: integer;
Module: TfrmChartBaseForm;
procedure Execute; override;
property Terminated;
end;
procedure TfrmChartBaseForm.FormCreate(Sender: TObject);
...
TheThread := TThreadClient.Create(true);
with TheThread do begin
Module := self;
FreeOnTerminate := true;
end;//with
end;//FormCreate
procedure TfrmChartBaseForm.WMThreadComm(var Message: TMessage);
begin
ListenThreadEvents(Message);
end;//WMThreadComm
procedure TfrmChartBaseForm.ListenThreadEvents(var Message: TMessage);
begin
// do override in derived classes
end;//ListenThreadEvents
procedure TfrmChartBaseForm.ExecuteInThread(AThread: TThreadClient);
begin
// do override in derived classes
end;//ExecuteInThread
procedure TThreadClient.Execute;
begin
with Module do begin
ExecuteInThread(self);
end;//with
end;//Execute
Furthermore, using VFI, I also have two units:
unit dtmPDoxClientDataOIU;
TdtmPDoxClientDataOI = class(TdtmPDoxClient)
cdsClient_IS: TClientDataSet;
...
dsr_I: TDataSource;
...
private
public
end;
unit frmPDoxClientDataOIU;
TfrmPDoxClientDataOI = class(TfrmChartBaseForm)
ChartOI: TChart;
...
procedure FormCreate(Sender: TObject);
public
{ Public declarations }
dtmPDoxClientDataOI: TdtmPDoxClientDataOI;
procedure ListenThreadEvents(var Message: TMessage); override;
procedure ExecuteInThread(AThread: TThreadClient); override;
end;
procedure TfrmPDoxClientDataOI.FormCreate(Sender: TObject);
begin
inherited;
dtmPDoxClientDataOI := TdtmPDoxClientDataOI.Create(self);
TheThread.Task := 1;
TheThread.Resume;
end;//FormCreate
procedure TfrmPDoxClientDataOI.ListenThreadEvents(var Message: TMessage);
begin
if (Message.WParam = 1) then begin
case Message.LParam of
//GUI tasks, using ClientDataset already compiled and not re-used
end;//case
end;//if
end;//ListenThreadEvents
procedure TfrmPDoxClientDataOI.ExecuteInThread(AThread: TThreadClient);
begin
while not AThread.Terminated and (AThread.Task <> 0) do begin
case AThread.Task of
1: begin
if dtmPDoxClientDataOI.GetData(...) then
if not AThread.Terminated then begin
PostMessage(Handle,WM_THREADCOMM,1,1);
AThread.Task := 2;
end //if
else
AThread.Task := 0;
end;//1
... etc...
end;//case
end;//while
end;//ExecuteInThread
So, when the final user presses the button, a new form and its own datamodule and
thread are created; the thread uses its own datamodule by means of ExecuteInThread
function. When data are ready, a PostMessage is sent to the form, which updates
the chart.
Like Lieven writes, it depends.
If you have database components on the datamodule, you have to know if the are thread safe, or to make them threadsafe.
Some database components require a seperate session object per thread.
There is a problem where you work with datamodule in Thread:
If you terminate your thread in OnDestroy event of form and are waiting for it (WaitFor) - you'll have a deadlock.
Main UI thread set lock
procedure TCustomForm.BeforeDestruction;
begin
GlobalNameSpace.BeginWrite;
and your thread will wait infinitely in it's datamodule destructor with the same
destructor TDataModule.Destroy;
begin
if not (csDestroying in ComponentState) then GlobalNameSpace.BeginWrite;
So, if you want to wait for your threads when close MainForm, do it in OnClose event or in Project's main file
Or you can destroy it in Synchronize

Resources