Delphi ADO EDatabaseError connecting to access database from different threads - multithreading

I am attempting to open a connection to an Access database using a TADOConnection created on separate threads. On opening the connection an EDatabaseError 'External exception C06D007F' is thrown in Data.Win.ADODB. I tried searching and was only able to find examples adding calls to CoInitialize/CoUninitialize, and am not sure what else is wrong with my code. The same connection code succeeds when not executed in a TTask or TThread.
Update: The code works when I compile the test app with 32 bit, but fails with 64 bit. I have both Access 32 bit and 64 bit database engines installed on my computer. I uninstalled the 64 bit engine and reinstalled with the latest one from Microsoft and the errors went away.
const
ConnectionString: string = 'Provider=Microsoft.ACE.OLEDB.16.0;Data Source="C:\Users\Public\Documents\TestDatabase.mdb"';
function TMainForm.CreateADOConnection(const AConnectionString: string): TADOConnection;
begin
Result := TADOConnection.Create(nil);
Result.ConnectionString := AConnectionString;
Result.LoginPrompt := False;
end;
procedure TMainForm.CreateDBConnections;
var
LTasks: array of ITask;
begin
SetLength(LTasks, 2);
LTasks[0] := TTask.Create(procedure
var
LConn1: TADOConnection;
begin
CoInitialize(nil);
try
LConn1 := CreateADOConnection(ConnectionString);
try
LConn1.Open;
LConn1.Close;
finally
LConn1.Free;
end;
finally
CoUninitialize;
end;
end);
LTasks[1] := TTask.Create(
procedure
var
LConn2: TADOConnection;
begin
CoInitialize(nil);
try
LConn2 := CreateADOConnection(ConnectionString);
try
LConn2.Open;
LConn2.Close;
finally
LConn2.Free;
end;
finally
CoUninitialize;
end;
end);
LTasks[0].Start;
LTasks[1].Start;
TTask.WaitForAll(LTasks);
end;

Related

Including a summary of the files to download with Inno Setup 6.1.1 beta to Ready page?

With Inno Download Plugin I had a code that registers a list of files to download and adds the list to "Ready" page memo at the same time:
Building memo text for Inno Download Plugin
I have modified the code to work with Inno Setup 6.1.1 download page, instead of IDP:
procedure AddFileForDownload(Url, FileName: string);
begin
DownloadPage.Add(Url, FileName, '');
FilesToDownload := FilesToDownload + ' ' + ExtractFileName(FileName) + #13#10;
Log('File to download: ' + Url);
end;
Then I adjusted NextButtonClick like this:
function NextButtonClick(CurPageID: integer): boolean;
begin
Result := True;
if (CurPageID = wpReady) then
begin
DownloadPage.Clear;
if (dotNetNeeded) then begin
{ We need to download the 4.6.2 setup from the Microsoft Website }
dotNetRedistPath := ExpandConstant('{tmp}\NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
AddFileForDownload(dotnetRedistURL, 'NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
end;
if (bVcRedist64BitNeeded) then
begin
{ We need to download the 64 Bit VC Redistributable from the Microsoft Website }
vcRedist64BitPath := ExpandConstant('{tmp}\vc_redist.x64.exe');
AddFileForDownload(vcRedist64BitURL, 'vc_redist.x64.exe');
end;
if (bVcRedist32BitNeeded) then
begin
{ We need to download the 32 Bit VC Redistributable from the Microsoft Website }
vcRedist32BitPath := ExpandConstant('{tmp}\vc_redist.x86.exe');
AddFileForDownload(vcRedist32BitURL, 'vc_redist.x86.exe');
end;
if (WizardIsTaskSelected('downloadhelp')) then
AddFileForDownload('{#HelpDocSetupURL}', 'HelpDocSetup.exe');
DownloadPage.Show;
try
try
DownloadPage.Download;
Result := True;
except
SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
Result := False;
end;
finally
DownloadPage.Hide;
end;
end;
end;
I ran the installer, and checked the wizard option to download the help documentation, and yet the Ready page displays only:
The Download section is not being added. How can that be? When I click Next it does continue to the next page to download the file.
I added some extra logging for FilesToDownload and it is interesting:
2020-11-01 11:44:22.409 UpdateReadyMemo FileToDownload:
2020-11-01 11:44:25.671 File to download: https://www.publictalksoftware.co.uk/downloads/MSAHelpDocumentationSetup.exe
2020-11-01 11:44:25.671 FileToDownload: HelpDocSetup.exe
The UpdateReadyMemo method is being called before we populate the variable. Confused!
I got confused a bit initially. Because the issue is obvious. Your code executes when you click "Install" button on the "Ready" page. So obviously only after the "Ready" page shows.
You have to call the AddFileForDownload earlier. Maybe to NextButtonClick(wpSelectTasks).
function NextButtonClick(CurPageID: integer): boolean;
begin
Result := True;
if (CurPageID = wpSelectTasks) then
begin
DownloadPage.Clear;
if (dotNetNeeded) then
begin
// We need to download the 4.6.2 setup from the Microsoft Website
dotNetRedistPath :=
ExpandConstant('{tmp}\NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
AddFileForDownload(
dotnetRedistURL, 'NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
end;
if (bVcRedist64BitNeeded) then
begin
// We need to download the 64 Bit VC Redistributable
// from the Microsoft Website
vcRedist64BitPath := ExpandConstant('{tmp}\vc_redist.x64.exe');
AddFileForDownload(vcRedist64BitURL, 'vc_redist.x64.exe');
end;
if (bVcRedist32BitNeeded) then
begin
// We need to download the 32 Bit VC Redistributable
// from the Microsoft Website
vcRedist32BitPath := ExpandConstant('{tmp}\vc_redist.x86.exe');
AddFileForDownload(vcRedist32BitURL, 'vc_redist.x86.exe');
end;
if (WizardIsTaskSelected('downloadhelp')) then
AddFileForDownload('{#HelpDocSetupURL}', 'HelpDocSetup.exe');
end
else
if (CurPageID = wpReady) then
begin
DownloadPage.Show;
try
try
DownloadPage.Download;
Result := True;
except
SuppressibleMsgBox(
AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
Result := False;
end;
finally
DownloadPage.Hide;
end;
end;
end;
(untested)

Inno Setup check version of external application

What I'm trying to achieve is to check if Node.js is already is installed, and if so I wanna check for the version being up to date, let's say 8.x.x
From the question below I already achieved the initial check for it being installed at all. My Code looks pretty similar to the answer of the question.
Using Process Exit code to show error message for a specific File in [Run]
Now I'm struggling with reading the actual output of the node -v command (Expected result a string containing the version).
Is there a way to achieve that?
Running an application and parsing its output is rather inefficient way to check, if it exists and its version. Use FileSearch (node.exe is added to PATH) and GetVersionNumbers functions instead.
[Code]
function CheckNodeJs(var Message: string): Boolean;
var
NodeFileName: string;
NodeMS, NodeLS: Cardinal;
NodeMajorVersion, NodeMinorVersion: Cardinal;
begin
{ Search for node.exe in paths listed in PATH environment variable }
NodeFileName := FileSearch('node.exe', GetEnv('PATH'));
Result := (NodeFileName <> '');
if not Result then
begin
Message := 'Node.js not installed.';
end
else
begin
Log(Format('Found Node.js path %s', [NodeFileName]));
Result := GetVersionNumbers(NodeFileName, NodeMS, NodeLS);
if not Result then
begin
Message := Format('Cannot read Node.js version from %s', [NodeFileName]);
end
else
begin
{ NodeMS is 32-bit integer with high 16 bits holding major version and }
{ low 16 bits holding minor version }
{ shift 16 bits to the right to get major version }
NodeMajorVersion := NodeMS shr 16;
{ select only low 16 bits }
NodeMinorVersion := NodeMS and $FFFF;
Log(Format('Node.js version is %d.%d', [NodeMajorVersion, NodeMinorVersion]));
Result := (NodeMajorVersion >= 8);
if not Result then
begin
Message := 'Node.js is too old';
end
else
begin
Log('Node.js is up to date');
end;
end;
end;
end;
function InitializeSetup(): Boolean;
var
Message: string;
begin
Result := True;
if not CheckNodeJs(Message) then
begin
MsgBox(Message, mbError, MB_OK);
Result := False;
end;
end;
Since Inno Setup 6.1, you can use GetVersionComponents instead of GetVersionNumbers to avoid the bit magics.
For a similar question, see Checking if Chrome is installed and is of specific version using Inno Setup.

How to set StatusMsg from PrepareToInstall event function

My application requires .NET Framework to be installed so I run .NET installation in PrepareToIntall event function. While the installation is running I would like to display some simple message on Wizard.
I found How to set the status message in [Code] Section of Inno install script? but the solution there doesn't work for me.
I tried
WizardForm.StatusLabel.Caption := CustomMessage('InstallingDotNetMsg');
and also
WizardForm.PreparingLabel.Caption := CustomMessage('InstallingDotNetMsg');
EDIT
I have to do this in PrepareToInstall function, because I need to stop the setup when .net installation fails.
Code looks like this right now:
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
isDotNetInstalled : Boolean;
errorCode : Integer;
errorDesc : String;
begin
isDotNetInstalled := IsDotNetIntalledCheck();
if not isDotNetInstalled then
begin
//WizardForm.PreparingLabel.Caption := CustomMessage('InstallingDotNetMsg');
WizardForm.StatusLabel.Caption := CustomMessage('InstallingDotNetMsg');
ExtractTemporaryFile('dotNetFx40_Full_x86_x64.exe');
if not ShellExec('',ExpandConstant('{tmp}\dotNetFx40_Full_x86_x64.exe'),'/passive /norestart', '', SW_HIDE, ewWaitUntilTerminated, errorCode) then
begin
errorDesc := SysErrorMessage(errorCode);
MsgBox(errorDesc, mbError, MB_OK);
end;
isDotNetInstalled := WasDotNetInstallationSuccessful();
if not isDotNetInstalled then
begin
Result := CustomMessage('FailedToInstalldotNetMsg');
end;
end;
end;
Any Ideas how to achieve this?
The StatusLabel is hosted by the InstallingPage wizard page while you're on PreparingPage page in the PrepareToInstall event method. So that's a wrong label. Your attempt to set the text to the PreparingLabel was correct, but failed because that label is hidden by default (it is shown when you return non empty string as a result to the event method).
But you can show it for a while (you are using ewWaitUntilTerminated flag, so your installation is synchronous, thus it won't hurt anything):
[Code]
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
WasVisible: Boolean;
begin
// store the original visibility state
WasVisible := WizardForm.PreparingLabel.Visible;
try
// show the PreparingLabel
WizardForm.PreparingLabel.Visible := True;
// set a label caption
WizardForm.PreparingLabel.Caption := CustomMessage('InstallingDotNetMsg');
// do your installation here
finally
// restore the original visibility state
WizardForm.PreparingLabel.Visible := WasVisible;
end;
end;
Another solution is to use CreateOutputProgressPage to display a progress page over the top of the Preparing to Install page. See the CodeDlg.iss example script included with Inno for an example of the usage; it's fairly straightforward.

Uninstall when running inno setup with application already installed

I have just started using inno setup, and it seems to work well. However, when I run the installer with the app already installed it reinstalls. I would like to give the user to uninstall. Is this possible, and if so, how can it be done?
To be specific, I have written a game for a homework assignment. I made an installer using inno setup. The app installs fine and can be uninstalled using the control panel, but my professor would like to be able to uninstall the application by re-running the installer and choosing an uninstall option. This will save him time since he has about 50 of these assignments to mark.
Thanks,
Gerry
The next script will make the following options form when the application is already installed on the target system when the setup is started:
When the user clicks Repair button, the setup is normally started. When user clicks the Uninstall button, the previously installed application is uninstalled. When user closes that form, nothing happens.
Here is the script (don't forget to specify, ideally some unique, value for the AppId setup directive in your script):
[Setup]
AppName=My Program
AppVersion=1.5
AppId=1C9FAC66-219F-445B-8863-20DEAF8BB5CC
DefaultDirName={pf}\My Program
OutputDir=userdocs:Inno Setup Examples Output
[CustomMessages]
OptionsFormCaption=Setup options...
RepairButtonCaption=Repair
UninstallButtonCaption=Uninstall
[Code]
const
mrRepair = 100;
mrUninstall = 101;
function ShowOptionsForm: TModalResult;
var
OptionsForm: TSetupForm;
RepairButton: TNewButton;
UninstallButton: TNewButton;
begin
Result := mrNone;
OptionsForm := CreateCustomForm;
try
OptionsForm.Width := 220;
OptionsForm.Caption := ExpandConstant('{cm:OptionsFormCaption}');
OptionsForm.Position := poScreenCenter;
RepairButton := TNewButton.Create(OptionsForm);
RepairButton.Parent := OptionsForm;
RepairButton.Left := 8;
RepairButton.Top := 8;
RepairButton.Width := OptionsForm.ClientWidth - 16;
RepairButton.Caption := ExpandConstant('{cm:RepairButtonCaption}');
RepairButton.ModalResult := mrRepair;
UninstallButton := TNewButton.Create(OptionsForm);
UninstallButton.Parent := OptionsForm;
UninstallButton.Left := 8;
UninstallButton.Top := RepairButton.Top + RepairButton.Height + 8;
UninstallButton.Width := OptionsForm.ClientWidth - 16;
UninstallButton.Caption := ExpandConstant('{cm:UninstallButtonCaption}');
UninstallButton.ModalResult := mrUninstall;
OptionsForm.ClientHeight := RepairButton.Height + UninstallButton.Height + 24;
Result := OptionsForm.ShowModal;
finally
OptionsForm.Free;
end;
end;
function GetUninstallerPath: string;
var
RegKey: string;
begin
Result := '';
RegKey := Format('%s\%s_is1', ['Software\Microsoft\Windows\CurrentVersion\Uninstall',
'{#emit SetupSetting("AppId")}']);
if not RegQueryStringValue(HKEY_LOCAL_MACHINE, RegKey, 'UninstallString', Result) then
RegQueryStringValue(HKEY_CURRENT_USER, RegKey, 'UninstallString', Result);
end;
function InitializeSetup: Boolean;
var
UninstPath: string;
ResultCode: Integer;
begin
Result := True;
UninstPath := RemoveQuotes(GetUninstallerPath);
if UninstPath <> '' then
begin
case ShowOptionsForm of
mrRepair: Result := True;
mrUninstall:
begin
Result := False;
if not Exec(UninstPath, '', '', SW_SHOW, ewNoWait, ResultCode) then
MsgBox(FmtMessage(SetupMessage(msgUninstallOpenError), [UninstPath]), mbError, MB_OK);
end;
else
Result := False;
end;
end;
end;
For some reason your code
RegKey := Format('%s\%s_is1', ['Software\Microsoft\Windows\CurrentVersion\Uninstall',
'{#emit SetupSetting("AppId")}']);
returned an extra { to the _is1 value. I didn't had the time to check why or where i was wrong in my implementation,
all i confirm is that my installer works with the
RegKey := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
alternate.
Hope it helps.
Thank you for the code sample.
When using Inno Setup, there's no reason to uninstall a previous version unless that version was installed by a different installer program. Otherwise upgrades are handled automatically.
Your answer is here :
InnoSetup: How to automatically uninstall previous installed version? previous-installed-version

Global, thread safe, cookies manager with Indy

My Delphi 2010 app uploads stuff using multi-threading, uploaded data is POSTed to a PHP/web application which requires login, so I need to use a shared/global cookies manager (I'm using Indy10 Revision 4743) since TIdCookieManager is not thread-safe :(
Also, server side, session id is automatically re-generated every 5 minutes, so I must keep both the global & local cookie managers in sync.
My code looks like this:
TUploadThread = class(TThread)
// ...
var
GlobalCookieManager : TIdCookieManager;
procedure TUploadThread.Upload(FileName : String);
var
IdHTTP : TIdHTTP;
TheSSL : TIdSSLIOHandlerSocketOpenSSL;
TheCompressor : TIdCompressorZLib;
TheCookieManager : TIdCookieManager;
AStream : TIdMultipartFormDataStream;
begin
ACookieManager := TIdCookieManager.Create(IdHTTP);
// Automatically sync cookies between local & global Cookie managers
#TheCookieManager.OnNewCookie := pPointer(Cardinal(pPointer( procedure(ASender : TObject; ACookie : TIdCookie; var VAccept : Boolean)
begin
OmniLock.Acquire;
try
GlobalCookieManager.CookieCollection.AddCookie(ACookie, TIdHTTP(TIdCookieManager(ASender).Owner).URL{IdHTTP.URL});
finally
OmniLock.Release;
end; // try/finally
VAccept := True;
end )^ ) + $0C)^;
// ======================================== //
IdHTTP := TIdHTTP.Create(nil);
with IdHTTP do
begin
HTTPOptions := [hoForceEncodeParams, hoNoParseMetaHTTPEquiv];
AllowCookies := True;
HandleRedirects := True;
ProtocolVersion := pv1_1;
IOHandler := TheSSL;
Compressor := TheCompressor;
CookieManager := TheCookieManager;
end; // with
OmniLock.Acquire;
try
// Load login info/cookies
TheCookieManager.CookieCollection.AddCookies(GlobalCookieManager.CookieCollection);
finally
OmniLock.Release;
end; // try/finally
AStream := TIdMultipartFormDataStream.Create;
with Stream.AddFile('file_name', FileName, 'application/octet-stream') do
begin
HeaderCharset := 'utf-8';
HeaderEncoding := '8';
end; // with
IdHTTP.Post('https://www.domain.com/post.php', AStream);
AStream.Free;
end;
But it doesn't work! I'm getting this exception when calling AddCookies()
Project MyEXE.exe raised exception class EAccessViolation with message
'Access violation at address 00000000. Read of address 00000000'.
I also tried using assign(), ie.
TheCookieManager.CookieCollection.Assign(GlobalCookieManager.CookieCollection);
But I still get the same exception, usually here:
TIdCookieManager.GenerateClientCookies()
Anyone knows how to fix this?
Don't use an anonymous procedure for the OnNewCookie event. Use a normal class method instead:
procedure TUploadThread.NewCookie(ASender: TObject; ACookie : TIdCookie; var VAccept : Boolean);
var
LCookie: TIdCookie;
begin
LCookie := TIdCookieClass(ACookie.ClassType).Create;
LCookie.Assign(ACookie);
OmniLock.Acquire;
try
GlobalCookieManager.CookieCollection.AddCookie(LCookie, TIdHTTP(TIdCookieManager(ASender).Owner).URL);
finally
OmniLock.Release;
end;
VAccept := True;
end;
Or:
procedure TUploadThread.NewCookie(ASender: TObject; ACookie : TIdCookie; var VAccept : Boolean);
begin
OmniLock.Acquire;
try
GlobalCookieManager.CookieCollection.AddServerCookie(ACookie.ServerCookie, TIdHTTP(TIdCookieManager(ASender).Owner).URL);
finally
OmniLock.Release;
end;
VAccept := True;
end;
Then use it like this:
procedure TUploadThread.Upload(FileName : String);
var
IdHTTP : TIdHTTP;
TheSSL : TIdSSLIOHandlerSocketOpenSSL;
TheCompressor : TIdCompressorZLib;
TheCookieManager : TIdCookieManager;
TheStream : TIdMultipartFormDataStream;
begin
IdHTTP := TIdHTTP.Create(nil);
try
...
TheCookieManager := TIdCookieManager.Create(IdHTTP);
TheCookieManager.OnNewCookie := NewCookie;
with IdHTTP do
begin
HTTPOptions := [hoForceEncodeParams, hoNoParseMetaHTTPEquiv];
AllowCookies := True;
HandleRedirects := True;
ProtocolVersion := pv1_1;
IOHandler := TheSSL;
Compressor := TheCompressor;
CookieManager := TheCookieManager;
end; // with
OmniLock.Acquire;
try
// Load login info/cookies
TheCookieManager.CookieCollection.AddCookies(GlobalCookieManager.CookieCollection);
finally
OmniLock.Release;
end;
TheStream := TIdMultipartFormDataStream.Create;
try
with TheStream.AddFile('file_name', FileName, 'application/octet-stream') do
begin
HeaderCharset := 'utf-8';
HeaderEncoding := '8';
end;
IdHTTP.Post('https://www.domain.com/post.php', TheStream);
finally
TheStream.Free;
end;
finally
IdHTTP.Free;
end;
end;
If I had to guess, I'd say your problem is in here somewhere:
// Automatically sync cookies between local & global Cookie managers
#TheCookieManager.OnNewCookie := pPointer(Cardinal(pPointer( procedure(ASender : TObject; ACookie : TIdCookie; var VAccept : Boolean)
begin
OmniLock.Acquire;
try
GlobalCookieManager.CookieCollection.AddCookie(ACookie, TIdHTTP(TIdCookieManager(ASender).Owner).URL{IdHTTP.URL});
finally
OmniLock.Release;
end; // try/finally
VAccept := True;
end )^ ) + $0C)^;
I'm not sure what the $0C magic number is there for, but I bet all those casts are there because you had a heck of a time getting the compiler to accept this. It gave you type errors saying you couldn't assign the one thing to the other.
Those type errors are there for a reason! If you hack your way around the type system, things are very likely to break. Try turning that anonymous method into a normal method on TUploadThread and assign it that way, and see if it doesn't work better.
Responding to the comment:
Thank you guys, I converted to a normal method, but I'm still getting
exceptions in AddCookies(), last one happened in the line that reads
FRWLock.BeginWrite; in this procedure
TIdCookies.LockCookieList(AAccessType: TIdCookieAccess):
TIdCookieList;
If your error is an Access Violation with Read of address 00000000, that's got a very specific meaning. It means you're trying to do something with an object that's nil.
When you get that, break to the debugger. If the error's taking place on the line you said it's happening on, then it's almost certain that either Self or FRWLock is nil at this point. Check both variables and figure out which one hasn't been constructed yet, and that'll point you to the solution.

Resources