I would like to check MD5 of a file with this code:
[Code]
var
MD5Comp: string;
procedure ExitProcess(uExitCode:UINT);
external 'ExitProcess#kernel32.dll stdcall';
procedure CurStepChanged(CurStep: TSetupStep);
begin
MD5Comp := '32297BCBF4D802298349D06AF5E28059';
if CurStep = ssInstall then
begin
if not MD5Comp=GetMD5OfFile(ExpandConstant('{app}\cg.npa')) then
begin
MsgBox('A patched version detected. Setup will now exit.', mbInformation, MB_OK);
ExitProcess(1);
end;
end;
end;
But I get a "Type Mismatch" error when comparing the two string, so I am assuming that this is not how you compare them.
Edit: I've tried if not CompareText(MD5Comp,GetMD5OfFile(ExpandConstant('{app}\cg.npa')))=0 but it never execute what's inside the if.
This seems to be an exception of the Pascal Script compiler. You were expecting an expression like this (assume S1 and S2 are string variables):
if not (S1 = S2) then
But the compiler treated it like this instead:
if (not S1) = S2 then
Well, I would personally expect a compiler error instead of runtime one here. At least you have simple way to workaround this issue if you explicitly enclose that comparison into parentheses like:
if not (MD5Comp = GetMD5OfFile(ExpandConstant('{app}\cg.npa'))) then
Or optionally write more literally:
if MD5Comp <> GetMD5OfFile(ExpandConstant('{app}\cg.npa')) then
Do note, that in this case the parentheses are not needed because with the <> operator it becomes a single boolean expression.
Related
I am trying to get Inno Setup define value in Code section but not with {#VersionTool1}. I need to pass defined name dynamically, because there are a lot of them (I want to avoid large switch case). I tried SetupSetting but it's not in Setup section (it's before it). Is there any way to do this?
#define VersionTool1 2019.01.1111
#define VersionTool2 2020.02.2111
...
[Code]
procedure SetSelectedTool(ToolName: String);
var
CurrentTool: string;
begin
...
CurrentTool := 'Version' + ToolName;
CurrentToolVersion := {#CurrentTool};
...
end;
Value of local variable CurrentTool wil for example be 'VersionTool1' and I want to get value of VersionTool1 preprocessor variable which is 2020.02.2111.
It's not possible, see Evaluate preprocessor macro on run time in Inno Setup Pascal Script.
But there are other solutions.
For example:
[Code]
var
ToolNames: TStringList;
ToolVersions: TStringList;
function InitializeSetup(): Boolean;
begin
ToolNames := TStringList.Create;
ToolVersions := TStringList.Create;
#define AddToolVersion(Name, Version) \
"ToolNames.Add('" + Name + "'); ToolVersions.Add('" + Version +"');"
#emit AddToolVersion('Tool1', '2019.01.1111')
#emit AddToolVersion('Tool2', '2020.02.2111')
{ ... }
Result := True;
end;
(of course, the above makes sense only if you actually do not hardcode the version numbers, but use a code that only a preprocessor can do – something like GetStringFileInfo, what I've understood from your comments that you plan to)
And then you can have a function like:
function GetToolVersion(ToolName: string): string;
var
I: Integer;
begin
I := ToolNames.IndexOf(ToolName);
if I >= 0 then Result := ToolVersions[I];
end;
Another similar questions:
Array Variables and dynamic access in [Code] section
Scripting capabilities in the Registry section
So given this function, I get the error "Identifier Expected" on the GetRoot := ROOTPage.Values[0]; line. I expect it is telling me that the ROOTPage is not defined?
const
DefaultRoot = 'C:\IAmGRoot';
Var
ROOTPage : TInputQueryWizardPage;
procedure SetupRoot;
begin
ROOTPage := CreateInputQueryPage(wpUserInfo,
ExpandConstant('{cm:RootTitle}'),
ExpandConstant('{cm:RootInstructions}'),
ExpandConstant('{cm:RootDescription}') + ' "' + DefaultRoot + '"'
);
ROOTPage.Add(ExpandConstant('{cm:SSRoot}') + ':', False);
ROOTPage.Values[0] := ExpandConstant('{DefaultRoot}');
// add SSROOT to path
end;
function GetRoot : string;
begin
GetRoot := ROOTPage.Values[0];
end;
How should I interpret this error. What is an identifier in Pascal?
This page tells me that identifiers are variable names. Perhaps I need to expand the ROOTPage.Values[0] in some way since I am referencing an array from an Inno Setup object?
Or maybe I need to return the value differently. I saw one page on Pascal that said that you needed to avoid assigning to the function value on parameter less functions to avoid a recursive loop. Does this mean I should pass in a dummy value? or is there a different syntax? That page did not explain.
I secretly think my real issue is that I am not defining my function correctly... but well. That much compiles at least. This question could become: How do you handle a parameter-less function in Pascal?
I do not think Inno Setup is part of the issue, but I am working with Inno Setup in case that is important.
Update:
It doesn't seem to be the array because this gets the same error:
const
DefaultRoot = 'C:\IAmGRoot';
function GetRoot : string;
begin
GetRoot := DefaultRoot;
end;
Update:
This link has said that the function name can be replaced/ should be replaced with the key word Result such as the following code. I actually knew this, but the Inno Setup compiler does not recognize this as valid syntax. It then tells me my function is an invalid prototype.
function GetRoot : string;
begin
Result := DefaultRoot;
end;
Update:
If I do this I get "Invalid Prototype for GetRoot"
function GetRoot : boolean;
begin
Result := False;
end;
Update for #Martin Prikryl:
Well I use it a few places but the typical use would be like this:
[Files]
Source: "C:\ValidPath\Release\*"; DestDir: "{app}\bin"; Components: DefinedComponent
Source: "C:\ValidPath\Deployment\*"; DestDir: "{code:GetRoot}\"; Flags: ignoreversion recursesubdirs; Components: DefinedComponent
Identifier expected
Your code would be correct in a Pascal, but it does not compile in Pascal Script.
In Pascal, when you want to assign a return value of a function, you either assign the value to a "variable" with a name of the function or to Result variable.
So this is correct:
function GetRoot: string;
begin
GetRoot := ROOTPage.Values[0];
end;
And this too (both are equivalent):
function GetRoot: string;
begin
Result := ROOTPage.Values[0];
end;
In the Pascal Script, only the Result works. When you use the name of the function, you get the "Identifier expected."
Invalid prototype
You get this when the function is called from outside of the Code section and a specific parameter list/return value is required. But you didn't tell us, what you use the GetRoot function for.
There are two places, where you can use a custom function in Inno Setup:
Check parameter: For this the function must return a Boolean and take either no parameter or one parameter (the parameter type is determined by a value you provide in the Check parameter).
function MyProgCheck(): Boolean;
function MyDirCheck(DirName: String): Boolean;
Scripted Constants: The function must return a string and take one string parameter, even if no parameter is provided in the scripted constant. I assume this is your use case. If you do not need any parameter, just declare it, but do not use it:
function GetRoot(Param: String): string;
begin
Result := ROOTPage.Values[0];
end;
Is it possible to build a script line using a variable and some conditions (have executable that requires different switches depending on what is checked on a custom page. Possibly something like this:
var
linecommand : string;
function FileParams();
begin
if check1 then linecommand := linecommand+' conditionone' end;
if check2 then linecommand := linecommand+' conditiontwo' end;
if check3 then linecommand := linecommand+' conditiontwo' end;
end;
function check123()
begin
if check1 or check2 or check3 then
begin
linecommand='Executable file name.exe '+linecommand;
Shellexec(linecommand);
end;
end;
You can use a {code:...} constant to insert a value calculated in a [Code] function into a specific value within a standard entry that accepts both strings and constants (such as the Parameters value of a [Run] entry).
Or you can write multiple entries and use a Check function to determine which (if any) of them actually get executed.
I have GetVersion function in [Code] that returns a string like this "1004", "1003", etc.
I created this function to check the registry value for lowest version numbers and uninstall them.
Here is a snippet of the code it is giving error point to StrtoInt conversion line stating
Comma (,) expected
Here is the snippet:
function DoesOldVersionsExist(): Boolean;
var
AppVersion: integer;
mstr: string;
VersionInstalled: cardinal;
begin
AppVersion := StrToInt(GetVersion({#MyAppVersion}), 0);
...
after that line I'm simple comparing the values and return true or false. Much Appreciated.
This is what error message says:
Line 55
Column 40.
Comma (,) expected
Thanks Deanna but unfortunately that is the error message pointing to this :
AppVersion := StrToInt(GetVersion({#MyAppVersion}), 0);
^
Here is the GetVersion function:
function GetVersion(AppVersion: String): String;
var
Version: String;
CharIndex: integer;
c: char;
begin
for CharIndex := 1 to Length(AppVersion) do begin
c := AppVersion[CharIndex];
if (c <> '.') then
Version := Version + c;
end;
Result := Version;
end;
I think that you can't just have Inno Setup constants in code like this, you have to use ExpandConstant():
AppVersion := StrToInt(GetVersion(ExpandConstant('{#MyAppVersion}')), 0);
You have not given us enough information to give a definite answer, but I think that the situation is as follows.
You have defined some constant called MyAppVersion which you let the ISPP (the Inno Setup pre-processor) substitute. Now, you have not told us what type this variable is, and you have not told us what the signature of GetVersion is (in particular, what type of argument does it expect?). However, if these types are strings, you need to write
StrToInt(GetVersion('{#MyAppVersion}'), 0);
in order to obtain, say,
StrToInt(GetVersion('Some string, this is!'), 0);
instead of
StrToInt(GetVersion(Some string, this is!), 0);
which is malformed (indeed, to such an extent that it hurts my eyes to look at it).
I've got a almost completed app now and the next feature I want to implement is threading. I chose to go with BeginThread(), although am aware of TThread in delphi. The problem I'm coming across is the structure of BeginThread() call. Normally the line in the program that would call the function I want to be threaded is
CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
op is a integer.
The line I've switched it out for to create a thread from it is
BeginThread(nil,0,CompareFiles,Addr('form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op'),0,x);
From the little amount of infromation I can find on how to actually use BeginThread() this should be a fine call, however on compling all I get is complier errors regarding the structure of my BeginThread() statement paramenters.
EDIT FOR INFORMATION.
The current procedure that calls CompareFiles is
procedure TForm1.Panel29Click(Sender: TObject);
var
op,x : integer;
begin
if (Form1.Edit3.Text <> '') AND (Form1.Edit4.Text <> '') then
begin
op := 3;
if RadioButton7.Checked = True then op := 0;
if RadioButton3.Checked = True then op := 1;
if RadioButton4.Checked = True then op := 2;
if RadioButton5.Checked = True then op := 3;
if RadioButton6.Checked = True then op := 4;
CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
end;
end;
If I was to use TThread as suggested by a couple of people, and as displayed by Rob below, I'm confused at how a) I would pass op,Edit3/4.Text and StringGrid2 to the CompareFiles. Guessing from the example of TThread I've seen I thought I would replace the code above with TCompareFilesThread.Executeand the put the current code from Panel29Click into TCompareFilesThread.Create and then add
FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;
to this
FEdit3Text := Form1.Edit3.Text;
FEdit4Text := Form1.Edit4.Text;
FGrid := Form1.StringGrid2;
But I've got this nagging feeling that is totally off the mark.
That's not at all the way to use BeginThread. That function expects a pointer to a function that takes one parameter, but the function you're trying to call wants four. The one parameter you're giving to BeginThread for it to forward to the thread procedure is a string, but you evidently hope that some sort of magic will turn that string of characters into the values that those variables contain.
That's not how Delphi works, and even for the languages that can do something like that, it's generally discouraged to actually do it.
To pass multiple parameters to BeginThread, define a record with all the values you'll need, and also define a record pointer:
type
PCompareFilesParams = ^TCompareFilesParams;
TCompareFilesParams = record
Edit3Text,
Edit4Text: string;
Grid: TStringGrid;
Op: Integer;
end;
Change CompareFiles to accept a pointer to that record:
function CompareFiles(Params: PCompareFilesParams): Integer;
To start the thread, you'll need to allocate an instance of that record and populate its fields:
var
Params: PCompareFilesParams;
begin
New(Params);
Params.Edit3Text := Edit3.Text;
Params.Edit4Text := Edit4.Text;
Params.Grid := StringGrid2;
Params.Op := op;
BeginThread(nil, 0, #CompareFiles, Params, 0, x);
Implement CompareFiles like this so that the record will get freed before the thread terminates:
function CompareFiles(Params: PCompareFilesParams): Integer;
begin
try
// <Normal implementation goes here.>
finally
Dispose(Params);
end;
end;
You can make it all a lot easier if you just use TThread, though. You can make your descendant class have as many parameters as you want in its constructor, so you don't have to mess around with dynamically allocating and freeing a special record.
type
TCompareFilesThread = class(TThread)
private
FEdit3Text,
FEdit4Text: string;
FGrid: TStringGrid;
FOp: Integer;
procedure Execute; override;
public
constructor Create(const Edit3Text, Edit4Text: string; Grid: TStringGrid; Op: Integer);
property ReturnValue;
end;
constructor TCompareFilesThread.Create;
begin
inherited Create(False);
FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;
FOp := Op;
end;
procedure TCompareFilesThread.Execute;
begin
ReturnValue := CompareFiles(FEdit3Text, FEdit4Text, FGrid, FOp);
end;
Instead of calling BeginThread, you just instantiate the class and let it run:
var
ThreadRef: TThread;
ThreadRef := TCompareFilesThread.Create(Edit3.Text, Edit4.Text, StringGrid2, Op);
There's more to using threads, such as knowing when the thread has finished running, but I think you have enough to get started. One last thing to beware of, though, is that TStringGrid is a VCL control. You mustn't do anything with it from this new thread you create (regardless of how you end up creating it). Eveything you do with the grid control need to be done from the main thread. Use TThread.Synchronize and TThread.Queue to shift any VCL operations onto the main thread. Your file-comparing thread will wait for the synchronized operation to complete, but it will keep running without waiting for a queued operation to complete.