Inno Setup crashes in appendChild msxml - inno-setup

I want to modify xml file in Inno Setup - but installer crashes. I tried different things, and as result got sample code with problem
procedure testXml();
var
xmlDocLocal, nodeLocal: Variant;
begin
try
xmlDocLocal := CreateOleObject('MSXML2.DOMDocument');
xmlDocLocal.async := False;
xmlDocLocal.resolveExternals := False;
xmlDocLocal.loadXML('<root></root>');
nodeLocal := xmlDocLocal.CreateElement('element1');
xmlDocLocal.documentElement.appendChild(nodeLocal);
except
end;
end;
During second call, installer crashes on the appendChild method. What am I doing wrong ?

Three ideas: first, we're using InnoSetup, but for us the OleObject needs to be created with another string ending with the specific version 6.0:
try
XMLDoc := CreateOleObject('MSXML2.DOMDocument.6.0');
except
RaiseException('Please install MSXML first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)');
end;
Second idea: try adding an xml header to the XML string you have in your code. Like this:
xmlDocLocal.loadXML('<?xml version="1.0" encoding="UTF-8" ?><root></root>');
Third idea: try checking for errors (as I already showed in the first snippet). That might give you a pretty good idea what goes wrong. This is how we do it (and it works):
XMLDoc.load(XMLFileName);
if XMLDoc.parseError.errorCode <> 0 then
XMLDoc.load(XMLFileName2);
if XMLDoc.parseError.errorCode <> 0 then
RaiseException('Error on line ' + IntToStr(XMLDoc.parseError.line) + ', position ' + IntToStr(XMLDoc.parseError.linepos) + ': ' + XMLDoc.parseError.reason);
Hope this helps you. Hard to solve an unknown issue ;-)

Though this is an old issue, I would like to bring it up once more. I am using InnoSetup 6 and have spent two days working on this until I found this stackoverflow issue. For me it seems that the problem is still there. My installer keeps crashing with an Access Violation and I boilt it down to a very similar example like the one above. It makes no difference if I use createElement or createNode.
xmlDocument := CreateOleObject('MSXML2.DOMDocument.6.0');
xmlDocument.async := false;
xmlDocument.resolveExternals := false;
xmlDocument.loadXML('<broker><web bind="http://localhost:8161"></web></broker>');
//xmlDocument.setProperty('SelectionLanguage', 'XPath');
// Select the <web> node
node := xmlDocument.selectSingleNode('/broker/web');
// save attribute value into variable
bind := node.getAttribute('bind');
// remove legacy attribute
node.removeAttribute('bind');
// add new <binding> element as first child of <web>
//newNode := xmlDocument.createNode(1, 'binding', '');
newNode := xmlDocument.createElement('binding');
newNode.setAttribute('uri', bind);
Log(Format('### Appending %s as first child of %s', [newNode.xml, node.xml]));
node.appendChild(newNode);
Log(Format('### Inserted %s as first child of %s', [newNode.xml, node.xml]));
xmlDocument.Save(bootConfig);
All I see when running the code above is this:
The difference with createElement and createNode is, that createElement creates the exception message above and createNode simply kills the installer silently.
The last I see in the logs is this line:
2022-04-25 13:44:47.597 ### Appending <binding uri="http://localhost:8161"/> as first child of <web></web>
2022-04-25 13:44:47.597 CurStepChanged raised an exception.
2022-04-25 13:44:47.597 Exception message:
2022-04-25 13:44:47.597 Message box (OK):
Runtime error (at 211:2827):
Access violation at address 03CC8380. Execution of address 03CC8380.
Has this been addressed in some way? I cannot see from Russel Jordan's site that there has been any bugfix for this.

Related

Inno Setup - Check if a component is installed

What I really want to do is have Inno Setup uninstall a component, if it's unchecked in a subsequent run. But, if I'm not mistaken, that is not possible in Inno Setup (actually, correct me, if I'm wrong on this).
So, instead I want to make check function to see if a component is installed, so I can hide it during subsequent runs. I'm not sure where else to get that info other than the Inno Setup: Selected Components under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\[AppName]_is1.
Now the problem is my Inno Setup: Selected Components is as,as2,as3,bs,bs2,bs3.
How can I detect as, without detecting as2 or as3?
Indeed, Inno Setup does not support uninstalling components.
For a similar question (and maybe better), see:
Inno Setup: Disable already installed components on upgrade
For checking of installed components, I'd rather suggest you to check for existence of files corresponding to the component.
Anyway, to answer your actual question: If you want to scan the Inno Setup: Selected Components entry, you can use this function:
function ItemExistsInList(Item: string; List: string): Boolean;
var
S: string;
P: Integer;
begin
Result := False;
while (not Result) and (List <> '') do
begin
P := Pos(',', List);
if P > 0 then
begin
S := Copy(List, 1, P - 1);
Delete(List, 1, P);
end
else
begin
S := List;
List := '';
end;
Result := (CompareText(S, Item) = 0);
end;
end;
Note that the uninstall key can be present in HKCU (not in HKLM) under certain circumstances.

Reducing overhead when accessing objects used by thread

A thread is looping through a list of 1000 objects every second.
The objects hold simple configuration data. When some conditons are met, a worker thread is given the configuration data and does some work based on that.
Now I want to bring up a settings dialog with the configuration data so I can change the data inside such an object. But then I have to access objects in the list while the thread is also continously accessing them. I know how to use a critical section, but if the thread enters a critical section each time it checks on an object, then the critical section will be entered and left 1000 times per second. Maybe there is a smarter way?
How to make threadsafe with the least overhead when:
a) loading the config data into the settings dialog form (which uses a TListView in virtual mode and needs to access the list of objects on demand)
b) and saving the form input back to the object?
EDIT: More detail was requested.
The objects are in a TList and basically look like this:
TConfigData = class
ID:Integer;
Name: String;
SwitchTime: TDateTime;
end;
The data of the ConfigData object needs to be loaded into the Settings Dialog form so it can be edited, and then, if the user clicks OK, the ConfigData object should be updated and the thread will happily use this new data next time the obkect is accessed. However, updating must not happen at the same time as the thread is reading the ConfigData object.
EDIT 2: Additional details:
The threads are reading ID, Name and SwitchTime but only SwitchTime is changed by threads. (When work is done, new time is calculated and thats what triggers next work event).
The settings dialog can change both Name and SwitchTime, but not ID.
After a bit of thought, you can get away without using critical sections at all just by using InterlockedExchangePointer:
You need to add a routine to update the config for an item:
procedure TMyThread.UpdateConfig(const aIndex: Integer; const aID:Integer;
const aName: String; const aSwitchTime: TDateTime);
var
newConfigToEdit: TConfigData;
oldValue: TConfigData;
begin
newConfigToEdit := TConfigData.Create;
newConfigToEdit.ID := aID;
newConfigToEdit.Name := aName;
newConfigToEdit.SwitchTime := aSwitchTime;
repeat
oldvalue := InterlockedExchangePointer(FConfigDataList.List[aIndex], newConfigToEdit);
until (oldvalue <> nil) and (oldValue <> newConfigToEdit);
oldvalue.Free; // Free the replaced one
end;
That routine would replace the config for the item with Index of aIndex. To get the config in your thread you will need to be a bit clever. We get a copy of it and replace the value in the list with nil while we are working on it. This prevents the other thread from replacing it. Once we have finished we put back the replaced value.
procedure TMyThread.Execute;
var
configToUse: TConfigData;
begin
repeat
// Get the config and replace it with nil so it won't be changed
configToUse := InterlockedExchangePointer(FConfigDataList.List[idx], nil);
// Do stuff with the config
// Put the config back
FConfigDataList.List[idx] := configToUse;
// You could also use the line below instead of the assignment
// InterlockedExchangePointer(FConfigDataList.List[idx], configToUse);
until Terminated;
end;
If you want to kick off a worker thread with the config then you should make a clone of it and then pass in the clone to the worker because it can be changed.
Main thread (pseudo)code (ObjList is the global variable):
if ConfigUpdated(i) then
ObjList[i].ConfigVersion := ObjList[i].ConfigVersion + 1;
Other thread(s) (pseudo)code (ObjConfVer is the local for thread)
for i := 0 to ObjCount - 1 do
if ObjConfVer[i] < ObjList[i].ConfigVersion then
begin
// Here you have to take care that main thread will can not change config while you handling it
// Do something about new config
ObjConfVer[i] := ObjList[i].ConfigVersion;
// Exit from critical section
end;
If you have n threads which works with the same ObjList it is allow to each thread doing something about changed config independently.
BTW if you are using FPC/Lazarus this link may be usefull: Parallel procedures

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.

Handling exceptions in TThread.Execute to make it uninterruptible

There is a multi-threaded app, which runs 24/7. A correct disposal of resources as well as a proper exceptions handling (including EAccessViolation) are key factors.
I am a little bit stuck on understanding how to correctly nest exception-handling blocks within a thread functions.
Among TMyThread.Execute there are two helper-function:
function LoadHtml(const AUrl: sting): string - a simple wrapper for TIdHTTP.Get()
function ParsePage(const Id: string): TOffers - a parser/DB-updater function
Execute starts querying database for the initial recordset of IDs. Then it starts a while not rs.Eof do loop where calls ParsePage which is a main processor.
ParsePage loads HTML (care of LoadHtml), then performs some string parsing operations and finally updates database.
Here is a code structure: (details omitted for brevity)
{Wrapper-function to load HTML page}
function TMyThread.LoadHtml(const AUrl: string): string;
var
Response: TStringStream;
HTTP: TIdHTTP;
begin
Result := '';
Response := TStringStream.Create('');
try
try
HTTP := TIdHTTP.Create(nil);
HTTP.ReadTimeout := 10000;
HTTP.Response.KeepAlive := false;
try
HTTP.Get(AUrl, Response);
if HTTP.ResponseCode = 200 then Result := Response.DataString;
finally
HTTP.Free;
end;
finally
Response.Free;
end;
except
//This code will run only on exception and *after* freeing all resources?
on E: EIdHTTPProtocolException do
if E.ErrorCode = 404 then
raise EMyOwnHTTPNotFoundError.Create('Page not found');
else
HandleErrorAndLogItToDB(E.Class);
end;
end;
{Loads HTML, processes it and updates DB}
function TMyThread.ParsePage(const Id: string): TOffers;
var
RawHTML: string;
Offer: TOffer; //a simple record to store key offer details;
begin
Result := TOffers.Create;
try {top-level try..except block}
try {Critical request. If it fails I want to move}
RawHTML := LoadHtml('http://onlinetrade.com/offer.html?id=' + Id);
except
on E: EMyOwnHTTPNotFoundError do {Defined in function LoadHtml()}
//Update DB: product does not exist.
else
HandleErrorAndLogItToDB(E.Class);
end;
end;
try
//Preform some basing string operations on RawHTML
except
on E: Exception do HandleErrorAndLogItToDB(E.Class);
end;
try {Iterate through some blocks of data and put them in the Offers: TList}
for i := 0 to N do
begin
//Set up TOffer record
Result.Add(Offer);
end
finally
FreeAndNil(Offer);
end;
except
on E: Exception do
begin
HandleErrorAndLogItToDB(E.Class);
FreeAndNil(Result);
raise; {does this return control to Execute?}
end;
end;
end;
Now Execute:
procedure TMyThread.Execute;
var
j: Integer;
s: string;
Offers: TOffers; {Is a simple TList to store a collection of TOffer (record)}
begin
inherited;
CoInitialize(nil); {ADO is in da house}
try {top-level try..except block}
try {nested try..finally to call CoUninitialize}
try {A critical operation which sources all further operations}
rs := AdoQuery('GetSomeRecords ' + IntToStr(SomeId));
except
on E: Exception do
begin
HandleErrorAndLogItToDB(E.Class);
Exit; {DB-query error means no reason to continue}
end;
end;
while not rs.EOF do
begin
try //a loop top-level try..except handler
Offers := ParsePage(rs.Fields['Id'].Value);
try //nested resource freeer
begin
try //nested try..except to handle DB queries
for j := 0 to N do with Offers.Items[j] do
AdoUpdateDB; //Update DB
Synchronize(UpdateProgressBar);
except
on E: Exception do
begin
HandleErrorAndLogItToDB(E.Class);
Continue; //as suggested
raise; //as suggested
end;
end;
rs.MoveNext;
end;
finally
FreeAndNil(Offers);
end;
except
on E: Exception do HandleErrorAndLogItToDB(E.Class);
end;
end; //end while..do loop
Synchronize(ResetProgressBar);
finally
CoUnitialize;
end;
except
on E: Exception do
begin
//Make everything possible to keep the thread running. No matter of:
//- HTTP/404 - Not Found exceptions (which I handle)
//- UpdateDatabase fails
//- String operation exceptions
//If anything critical occurs, Execute() shall just go to the next offer
//even if the current one is not properly processed.
end;
end;
Looking at this code I think, I try to handle too many exceptions which I probably do not need to handle, just passing them to the most outer try..except handler in Execute. There are just a couple of exceptions I really need to handle: initial database query and EMyOwnHTTPNotFoundError (to set a flag that the offer doesn't exist). I read somewhere a suggestion not to explicitly chase for exception handling unless you really need it...
However it is very important that the thread keeps on running no matter of which exceptions are thrown inside/outside any code blocks. The idea is to completely ignore exceptions and never break either while..do loop or stop the thread. At the same time, correctly disposing resources is also a must.
I would be grateful for any suggestions/comments on how to improve this code.
There is no good way to "handle" an access violation, or really any exception that does not indicate a specific condition that your code has already planned for. (For example, a File Not Found exception can be handled in a good way if you can simply tell the user to ask for another file.)
If an exception is raised as the result of a bug, it means something is happening in your code that you did not plan for. Your code rests on a bunch of assumptions about things going right, things working as planned, and when an unexpected exception is raised, it means that those assumptions no longer necessarily hold. The best thing to do at that point is produce an error report to send back to you, and then shut down as quickly as possible.
Why? Because one of your assumptions that may no longer hold is that "critical data is in a valid, non-corrupted state." If the program keeps going, blindly following the assumption that all its data is good and then acting on it, it can turn a small problem into a much bigger one very, very quickly.
I completely understand the desire to make the program keep on going no matter what, but unfortunately it fundamentally conflicts with reality. The only sane thing to do when you get an unhandled exception--especially something like an access violation that can only be the result of buggy code of some variety--is to produce an error report and shut down.
If downtime is a Very Bad Thing, you could have something in place to make sure to start back up again as quickly as possible. This will keep you running, but it will reset your invariants (fundamental assumptions) and clear out the corrupt data. But for the love of all that is binary, shut the program down, and do it right away.
Then take the error report and fix your bug.

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;

Resources