Losing content of TThreadList - multithreading

I got the problem, that my content of my TThreadList seems to get lost in my Thread.
I create following variable in my "PConnect" Class:
var mCustomfunctionCallbackThread: CustomfunctionCallbackThread;
And a global variable like following:
var mCustomfunctionCallbackThreadList: TThreadList;
Now I'm filling this variable in my "PConnect" Class like following:
function PConnect.callCustomfunction(plugin, method: PAnsiChar; param :CustomParam; callback: ICustomfunctionCallback; callId: PAnsiChar): integer;
var paramName: PAnsiChar;
var id: PAnsiChar;
var error: integer;
var callbackList: TList;
var customfunctionCallback : ^CustomfunctionCallbackObject;
begin
callbackList:= mCustomfunctionCallbackThreadList.LockList;
new (customfunctionCallback);
customfunctionCallback.callId:= id;
customfunctionCallback.callbackMethod:= callback;
callbackList.Add(customfunctionCallback);
mCustomfunctionCallbackThreadList.UnlockList;
exit(0);
end;
Following function will be called, after I a Callback received. This Function should append the other missing data to the TThreadList entry and start a Thread after that:
procedure PConnect.customfunctionCallbackReceived(param :CustomParam; callId: PAnsiChar; error: integer);
var customfunctionCallbackList: TList;
var it: TListEnumerator;
var callbackObject: ^CustomfunctionCallbackObject;
begin
customfunctionCallbackList:= mCustomfunctionCallbackThreadList.LockList;
it:= customfunctionCallbackList.GetEnumerator;
while(it.MoveNext) do
begin
callbackObject:= it.GetCurrent;
if strcomp(callbackObject.callId,callId) = 0 then
begin
callbackObject.param:= param;
callbackObject.error:= error;
break;
end;
end;
mCustomfunctionCallbackThreadList.UnlockList;
mCustomfunctionCallbackThread.Start();
end;
The Execute method of the Thread should get the content of the TThreadList and call a function with that parameters.
procedure CustomfunctionCallbackThread.Execute;
var callback: ICustomfunctionCallback;
var customfunctionCallbackList: TList;
var it: TListEnumerator;
var callbackObject: ^CustomfunctionCallbackObject;
var param: CustomParam;
var callId: PAnsiChar;
var error: Integer;
begin
customfunctionCallbackList:= mCustomfunctionCallbackThreadList.LockList;
it:= customfunctionCallbackList.GetEnumerator;
while(it.MoveNext) do
begin
callbackObject:= it.GetCurrent;
if callbackObject.error <> NULL then
begin
callback:= callbackObject.callbackMethod;
param:= callbackObject.param;
error:= callbackObject.error;
callId:= callbackObject.callId;
callback.callCustomfunctionCallback(param, callId, error);
customfunctionCallbackList.Remove(callbackObject);
break;
end;
end;
mCustomfunctionCallbackThreadList.UnlockList;
end;
And heres the Problem, the following variables just got rubbish or Null pointer:
param:= callbackObject.param;
error:= callbackObject.error;
callId:= callbackObject.callId;
I hope it was enough described :)
I would be happy about some help :)

When you do
customfunctionCallback.callId:= id
the variable id has not been initialised. Which means that when you later check
strcomp(callbackObject.callId,callId) = 0
its behaviour is ill-defined, because callbackObject.callId was never initialised. What happens then is that the ill-defined behaviour that you see is that the expression never evaluates to True and so you never assign to the param or error fields. Which is consistent with your observations.
I suspect that you should make callId be a string variable and copy into it the value of the parameter callId that was passed to PConnect.callCustomfunction. But since I don't know all the details of what you are doing, I can't be 100% sure of that.
Some other comments:
Do you really mean to name your class PConnect? Normally we prefix classes with T.
The code would read a lot easier if you used for in loops instead of while loops.
The loop that contains a call to Remove on the list is inefficient. You've already found the item, and calling Remove just searches the list once again. The way to do that loop is with a class for loop using an index. And when you find the item to be deleted, call Delete passing the item's index.
When you remove an item you fail to call Dispose which means that you leak memory.

Related

Downloading files defined in start arguments with Inno Setup Script [duplicate]

I am preparing an installer with Inno Setup. But I'd like to add an additional custom (none of the available parameters) command line parameters and would like to get the value of the parameter, like:
setup.exe /do something
Check if /do is given, then get the value of something. Is it possible? How can I do this?
With InnoSetup 5.5.5 (and perhaps other versions), just pass whatever you want as a parameter, prefixed by a /
c:\> myAppInstaller.exe /foo=wiggle
and in your myApp.iss:
[Setup]
AppName = {param:foo|waggle}
The |waggle provides a default value if no parameter matches. Inno setup is not case sensitive. This is a particularly nice way to handle command line options: They just spring into existence. I wish there was as slick a way to let users know what command line parameters the installer cares about.
BTW, this makes both #knguyen's and #steve-dunn's answers somewhat redundant. The utility functions do exactly what the built-in {param: } syntax does.
Further to #DanLocks' answer, the {param:*ParamName|DefaultValue*} constant is documented near the bottom of the Constants page:
http://www.jrsoftware.org/ishelp/index.php?topic=consts
I found it quite handy for optionally suppressing the license page. Here is all I needed to add (using Inno Setup 5.5.6(a)):
[code]
{ If there is a command-line parameter "skiplicense=true", don't display license page }
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False
if PageId = wpLicense then
if ExpandConstant('{param:skiplicense|false}') = 'true' then
Result := True;
end;
Inno Setup directly supports switches with syntax /Name=Value using {param} constant.
You can use the constant directly in sections, though this use is quite limited.
An example:
[Registry]
Root: HKCU; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; \
ValueName: "Mode"; ValueData: "{param:Mode|DefaultMode}"
You will more likely want to use switches in Pascal Script.
If your switch has the syntax /Name=Value, the easiest way to read its value is using ExpandConstant function.
For example:
if ExpandConstant('{param:Mode|DefaultMode}') = 'DefaultMode' then
begin
Log('Installing for default mode');
end
else
begin
Log('Installing for different mode');
end;
If you want to use a switch value to toggle entries in sections, you can use Check parameter and a auxiliary function, like:
[Files]
Source: "Client.exe"; DestDir: "{app}"; Check: SwitchHasValue('Mode', 'Client')
Source: "Server.exe"; DestDir: "{app}"; Check: SwitchHasValue('Mode', 'Server')
[Code]
function SwitchHasValue(Name: string; Value: string): Boolean;
begin
Result := CompareText(ExpandConstant('{param:' + Name + '}'), Value) = 0;
end;
Ironically it is more difficult to check for a mere presence of switch (without a value).
Use can use a function CmdLineParamExists from #TLama's answer to Passing conditional parameter in Inno Setup.
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Exit;
end;
end;
You can obviously use the function in Pascal Script:
if CmdLineParamExists('/DefaultMode') then
begin
Log('Installing for default mode');
end
else
begin
Log('Installing for different mode');
end;
But you can even use it in sections, most typically using Check parameter:
[Files]
Source: "MyProg.hlp"; DestDir: "{app}"; Check: CmdLineParamExists('/InstallHelp')
A related problem:
Add user defined command line parameters to /? window
If you want to parse command line arguments from code in inno, then use a method similar to this. Just call the inno script from the command line as follows:
c:\MyInstallDirectory>MyInnoSetup.exe -myParam parameterValue
Then you can call the GetCommandLineParam like this wherever you need it:
myVariable := GetCommandLineParam('-myParam');
{ ================================================================== }
{ Allows for standard command line parsing assuming a key/value organization }
function GetCommandlineParam (inParam: String):String;
var
LoopVar : Integer;
BreakLoop : Boolean;
begin
{ Init the variable to known values }
LoopVar :=0;
Result := '';
BreakLoop := False;
{ Loop through the passed in arry to find the parameter }
while ( (LoopVar < ParamCount) and
(not BreakLoop) ) do
begin
{ Determine if the looked for parameter is the next value }
if ( (ParamStr(LoopVar) = inParam) and
( (LoopVar+1) <= ParamCount )) then
begin
{ Set the return result equal to the next command line parameter }
Result := ParamStr(LoopVar+1);
{ Break the loop }
BreakLoop := True;
end;
{ Increment the loop variable }
LoopVar := LoopVar + 1;
end;
end;
This is the function I wrote, which is an improvement of Steven Dunn's answer. You can use it as:
c:\MyInstallDirectory>MyInnoSetup.exe /myParam="parameterValue"
myVariable := GetCommandLineParam('/myParam');
{ util method, equivalent to C# string.StartsWith }
function StartsWith(SubStr, S: String): Boolean;
begin
Result:= Pos(SubStr, S) = 1;
end;
{ util method, equivalent to C# string.Replace }
function StringReplace(S, oldSubString, newSubString: String): String;
var
stringCopy: String;
begin
stringCopy := S; { Prevent modification to the original string }
StringChange(stringCopy, oldSubString, newSubString);
Result := stringCopy;
end;
{ ================================================================== }
function GetCommandlineParam(inParamName: String): String;
var
paramNameAndValue: String;
i: Integer;
begin
Result := '';
for i := 0 to ParamCount do
begin
paramNameAndValue := ParamStr(i);
if (StartsWith(inParamName, paramNameAndValue)) then
begin
Result := StringReplace(paramNameAndValue, inParamName + '=', '');
break;
end;
end;
end;
Yes it is possible, you can use the ParamStr function in PascalScript to access all the commandline parameters. The ParamCount function will give you the number of commandline parameters.
Another possibility is to use GetCmdTail
In response to:
"With InnoSetup 5.5.5 (and perhaps other versions), just pass whatever you want as a parameter, prefixed by a /"
"#NickG, yes, every constant you can expand by the ExpandConstant function"
This is not the case. Trying to use a command line parameter in ExpandConstant in InnoSetup 5.5.6 results in a runtime error.
PS: I would have added a comment directly but apparently I dont have enough "reputation"
I've modified a little bit knguyen's answer. Now it's case insensitive (you can write en console /myParam or /MYPARAM) and it can accept default value. Also I fixed the case when you receive larger parameter then expected (for ex: /myParamOther="parameterValue" in place of /myParam="parameterValue". Now myParamOther doesn't match).
function GetCommandlineParam(inParamName: String; defaultParam: String): String;
var
paramNameAndValue: String;
i: Integer;
begin
Result := defaultParam;
for i := 0 to ParamCount do
begin
paramNameAndValue := ParamStr(i);
if (Pos(Lowercase(inParamName)+'=', AnsiLowercase(paramNameAndValue)) = 1) then
begin
Result := Copy(paramNameAndValue, Length(inParamName)+2, Length(paramNameAndValue)-Length(inParamName));
break;
end;
end;
end;
I found the answer: GetCmdTail.
You can pass parameters to your installer scripts. Install the Inno Setup Preprocessor and read the documentation on passing custom command-line parameters.

Inno Setup - Merging implementations of event functions that return boolean (like InitializeSetup)

I use this code to ask for a password:
Inno Setup - Move the password page before the welcome page (first page)
And this code for custom language selector:
Inno Setup - Language selector with VCL Styles
When I merge them, it does not work.
I need password before that the language selector, so this is no correct:
function InitializeSetup(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
Result := AskPassword();
end;
end;
And this way, with an incorrect password the setup continues.
function InitializeSetup(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Result := AskPassword();
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
end;
end;
Inno Setup 6
Inno Setup 6 has event attributes features that helps solving this problem.
Just make sure that each of your event implementation have an unique name, e.g. appending unique suffix. And add event attribute with the name of the implemented event.
[Code]
function InitializeSetup(): Boolean;
begin
Result := ...
end;
<event('InitializeSetup')>
function InitializeSetup2(): Boolean;
begin
Result := ...
end;
Inno Setup 5
In general, the easiest is to keep both implementations of the event function separate and add one wrapper implementation that call both.
function InitializeSetup1(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
Result := True;
end;
end;
function InitializeSetup2(): Boolean;
begin
Result := AskPassword();
end;
function InitializeSetup(): Boolean;
begin
{ Order the calls the way you want the checks to be performed }
Result :=
InitializeSetup2() and
InitializeSetup1();
end;
For more general discussion of the problem, see
Merging event function (InitializeWizard) implementations from different sources
Though in your specific case, it's more complicated, as you will also need to pass the password from the first instance to the other, similarly to how the language is passed from the first instance to the other.
So actually, the InitializeSetup2 (password) implementation will have to be similar like the InitializeSetup1 (language), not to ask for the password again.
I actually do not really understand, why you complicate the things so much by not asking for language before the password. It would actually make sense. To get a localized password prompt.

Inno Setup: CreateInputQueryPage isn't returning any value

Here is the script I'm using on Inno Setup. It is my first script, please understand if I'm asking something obvious.
It seems that the variable ServerAddress never has a value, even if I fill the input field. It looks like Page.Values[0] always returns an empty result. What is wrong with my code?
As you can see, I have made a test with a testvar variable to exclude it was a matter of variable scope, but it's not the case.
[Code]
var
Page: TInputQueryWizardPage;
ServerAddress: String;
testvar: String;
procedure InitializeWizard();
begin
Page := CreateInputQueryPage(wpWelcome,
'Server Informations', '',
'Please specify the IP address, then click Next.');
{ Add items (False means it's not a password edit) }
Page.Add('IP Address:', False);
ServerAddress := Page.Values[0];
testvar := 'testvalue';
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then begin
MsgBox(ExpandConstant('{app} '+testvar+' : '+ServerAddress),mbInformation,MB_OK);
SaveStringToFile(ExpandConstant('{app}')+'\config.txt', 'test'+ServerAddress, True);
end;
end;
The InitializeWizard event function is called (and finishes) before the wizard window is even shown.
So a value (that the user will enter in the future) can hardly be known at that point. You have to read the value only after the custom page is shown. Like in your CurStepChanged(ssPostInstall):
procedure CurStepChanged(CurStep: TSetupStep);
var
ServerAddress: string;
begin
if CurStep = ssPostInstall then
begin
{ This is the right time to read the value }
ServerAddress := Page.Values[0];
SaveStringToFile(ExpandConstant('{app}') + '\config.txt', ServerAddress, True);
end;
end;

Add another destination behind path read from registry

How is possible to add two subfolders behind a path read from registry?
[Code]
function GetDirName(Value: string): string;
var
InstallPath: string;
begin
Result := 'C:\Program Files (x86)\myapp\subfolder1\subfolder2';
if RegQueryStringValue(HKLM, 'SOFTWARE\Wow6432Node\myapp', 'RootPath', InstallPath) then
Result := InstallPath
else
// query the second registry value; if it succeeds, return the obtained value
if RegQueryStringValue(HKLM, '\SOFTWARE\myapp', 'RootPath', InstallPath) then
Result := InstallPath;
end;
When I got the destination from register I need to add behind it \subfolder1\subfolder2 and complete destination get to function GetDirName.
Can someone guide me?
Thanks a lot.
Just concatenate the two strings at the end of the GetDirName function:
function GetDirName(Value: string): string;
var
InstallPath: string;
begin
// expand path to the 32-bit Program Files folder with appended 'myapp' subfolder
Result := ExpandConstant('{pf32}\myapp');
// query value from 64-bit registry node (notice the used HKLM64 root key)
if RegQueryStringValue(HKLM64, 'SOFTWARE\myapp', 'RootPath', InstallPath) or
// query value from 32-bit registry node (notice the used HKLM32 root key)
RegQueryStringValue(HKLM32, 'SOFTWARE\myapp', 'RootPath', InstallPath) then
begin
Result := InstallPath;
end;
// ensure the path will have backslash and append the final subdirectory string
Result := AddBackslash(Result) + 'subfolder1\subfolder2';
end;

How to find a component index by it's name?

Why can't I get one of my component's index by using this line:
WizardForm.ComponentsList.FindComponent('core').ComponentIndex
If I'm wrong, can anyone point out for me a way to get that index of component?
Currently, there's no way to find index of a component item in the ComponentsList by the component Name parameter. The Name parameter is stored in the TSetupComponentEntry structure, which is held by the ComponentsList.ItemObject[i] object collection, but you can't access it due to a lack of missing pointer support in Inno Setup Pascal Script.
The only way how to uniquely identify a component in the ComponentsList is by its Description parameter. To find index of a certain component by its description in the ComponentsList, you can use this:
[Components]
Name: "mycomponent"; Description: "Component description"; Types: full
[Code]
...
var
ItemIndex: Integer;
begin
ItemIndex := WizardForm.ComponentsList.Items.IndexOf('Component description');
...
end;
I found a way to do this through WizardSelectedComponents.
Define in var this array of String in order to save components name:
ComponentsName: array of String;
Define this function and this procedure:
Function StringToArray(const Text: String; const Cut: String): array of String;
var
i: Integer;
k: Integer;
Begin
SetArrayLength(Result, 0);
if Cut = '' then Cut:= #1310;
Repeat
k:= Pos(Cut,Text);
if k = 1 then
begin
Delete(Text, 1, Length(Cut));
CONTINUE
end;
SetArrayLength(Result, GetArrayLength(Result) +1); i:= GetArrayLength(Result) -1;
if k = 0 then
Result[i]:=Text
else begin
Result[i]:= Copy(Text, 1, k -1); Delete(Text, 1, Length(Result[i]) + Length(Cut));
end;
Until Length(Text) * k = 0;
End;
procedure InitializeComponentsName();
var
I: Integer;
CheckBack: array of boolean;
begin
SetArrayLength(CheckBack, WizardForm.ComponentsList.ItemCount);
for I:=0 to WizardForm.ComponentsList.ItemCount-1 do
begin
//Saves state in CheckBack.
CheckBack[I]:=WizardForm.ComponentsList.Checked[I];
//Only checks non checked components.
if not CheckBack[I] then WizardForm.ComponentsList.Checked[I]:=true;
end;
//Saves components names in ComponentsName array
ComponentsName:=StringToArray(WizardSelectedComponents(false), ',');
//Unchecks components that was uncheck previouly.
//If we try to check a checked component it may crash the Inno program (tested)
for I:=0 to WizardForm.ComponentsList.ItemCount-1 do
begin
if not CheckBack[I] then WizardForm.ComponentsList.Checked[I]:=false;
end;
//LOG components name.
log('COMPONENTS NAME:');
for I:=0 to GetArrayLength(ComponentsName) -1 do
begin
log(ComponentsName[I]);
end;
end;
You must call this procedure on InitializeWizard.
Now we have all components name in ComponentsName array.
You can use getComponentName to get your component name:
//Returns component name by Index.
function getComponentName(Index: Integer): String;
begin
if ((Index>=0) and (Index<GetArrayLength(ComponentsName))) then Result:=ComponentsName[Index];
end;
and GetIndexComponent to get your Index component by name.
//Returns index component by Name. -1 if it doesn't exist.
function GetIndexComponent(ComponentName: String): Integer;
var
J: Integer;
begin
Result:= -1;
for J:=0 to GetArrayLength(ComponentsName)-1 do
begin
if (ComponentName=ComponentsName[J]) then
begin
Result:=J;
break;
end;
end;
end;
If you add another component to ComponentsList you must call InitializeComponentsName in order to update ComponentsList array.

Resources