Runtime exception when calling dll function with parameter in Inno Setup - visual-c++

I am successfully calling a function in a DLL from Inno Setup, however upon returning I get a Runtime Error...Exception: Access violation at address XXXXXXX. Write of address XXXXXX.
The function is declared as:
function CompleteInstall(szIntallPath: String) : Integer;
external 'CompleteInstall#files:InstallHelper.dll stdcall setuponly';
And called:
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then begin
CompleteInstall('Parm1'); // ExpandConstant('{app}')
end;
end;
There is no problem if I change the function to not take a parameter. It still occurs if I change it to take a single integer parameter or declare it as a function and change the function to be a void function with an integer parameter.
The called function does nothing but return:
__declspec(dllexport) int CompleteInstall(char* szInstallPath)
{
//AfxMessageBox ("Got here" /*szInstallPath*/, MB_OK);
return 1;
}

You have a mismatch of the calling conventions. Either make the DLL function use stdcall as well:
__declspec(dllexport) __stdcall int CompleteInstall(char* szInstallPath)
{
//AfxMessageBox ("Got here" /*szInstallPath*/, MB_OK);
return 1;
}
or change the function declaration to use cdecl instead of stdcall:
function CompleteInstall(szIntallPath: String) : Integer;
external 'CompleteInstall#files:InstallHelper.dll cdecl setuponly';

Although according to mghie (see comments) it shouldn't make a difference in this case, you might want to use PChar instead of String as that would be the more accurate equivalent of the C-declaration char*.
String is a Pascal-native type which is usually managed quite differently than a PChar (though apparently not so much in Inno's PascalScript).

Related

How to use NeedsRestart parameter of PrepareToTinstall function in Inno Setup?

In AllPagesExample.iss example file there's this part:
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
if SuppressibleMsgBox('Do you want to stop Setup at the Preparing To Install wizard page?', mbConfirmation, MB_YESNO, IDNO) = IDYES then
Result := 'Stopped by user';
end;
If PrepareToTinstall is an event function and I don't call it myself, how can I pass NeedsRestart parameter to it?
The NeedsRestart parameter is passed by a reference (the var keyword). So its value is returned from the function similarly to the return value of the function itself (Result).
As the documentation says:
Return a non empty string to instruct Setup to stop at the Preparing to Install wizard page, showing the returned string as the error message. Set NeedsRestart to True (and return a non empty string) if a restart is needed. If Setup is stopped this way, it will exit with a dedicated exit code as described in Setup Exit Codes.
So, what you want to do, is to assign a value to the parameter and Inno Setup will act accordingly once the event function exits.
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
Result := '';
if DoWeNeedRestartBeforeInstalling then
begin
Result := 'You need to restart your machine before installing';
NeedsRestart := True;
end;
end;

How do I get a boolean result from a function using OmniThreadLibrary?

I have a Delphi (Windows) application created using Delphi 10 that has some blocking calls that I would like to convert to using threads. Unfortunately for me, these are not procedures, but functions. (And information on how to return function results from threads appears to be much more limited.) I am trying to familiarize myself with the OmniThreadLibrary, since it seems to be the most flexible and best supported threading library for Delphi, but I'm having trouble with understanding how to do this.
I have been able to get the various OmniThreadLibrary routines to work well with procedures, but when I try to set up a function, I get an error about capturing the result. When I use OmniThreadLibrary's Future example as a starting point, I can get the function to work, but I can't figure out how to connect to the event monitor, how to send messages from the task, etc. So, it seems as if I'm overlooking something no matter which way I try to solve this problem.
Currently, my program does something like this:
If myPing(IPAddress) then
begin
//Do other things hereā€¦
end;
Because myPing is blocking, and I actually need it to wait until myPing returns true before processing further, the application gets sluggish during this process. I'd like to put the myPing call in a thread, which would solve the sluggishness problem, but I can't figure out how to do that in the form of a function using OmniThreadLibrary. (Unless I use a future, in which case I can't figure out how to connect to the Event Monitor.)
Edit 1: Since my original post, I have made a little progress. I was able to connect the Event Monitor to the Future by adding Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1) to my code, right after the function. However, I still can't figure out how to send messages to that event monitor from within the Future function.
Edit 2: I now have some sample code. My first attempt was similar to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end
);
end;
The basic function worked, but did not allow me to send any messages to the TOmniEventMonitor. I was able to get that part working by changing the code to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Now, I can successfully monitor the OnTaskTerminated event, but I still can't send messages to the Event Monitor from the task. By changing my code once again, I can access the task itself and send messages using task.Comm.Send(), but the messages don't reach the EventMonitor:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function(const task: IOmniTask): boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
task.Comm.Send(0,'Test 1');
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Here's a simple example on how to retrieve the function result from the async call. It does not use an "OmniEventMonitor" but instead calls a function once the async call returns ("Ping" is defined in PingU.pas, but not of importance here):
unit MainFormU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus;
type
TPingResultEvent = procedure (const bResult: Boolean) of object;
TOnTerminateTestForm = class(TForm)
LogMemo: TMemo;
MainMenu: TMainMenu;
PingMenu: TMenuItem;
procedure PingMenuClick(Sender: TObject);
private
procedure BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
procedure PingResult (const bResult: Boolean);
public
{ Public declarations }
end;
var
OnTerminateTestForm: TOnTerminateTestForm;
implementation
{$R *.dfm}
uses PingU, OtlParallel, OtlTaskControl;
procedure TOnTerminateTestForm.PingMenuClick (Sender: TObject);
var
sServer : String;
begin
if (InputQuery ('Ping computer', 'Computer name:', sServer)) then
if (sServer <> '') then
begin
PingMenu.Enabled := false;
LogMemo.Lines.Add (Format ('Pinging %s',[sServer]));
BackgroundPing (sServer, PingResult);
end; { if }
end; { TOnTerminateTestForm.PingMenuClick }
procedure TOnTerminateTestForm.BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
var
bResult : Boolean;
begin
Parallel.Async (
procedure
begin
bResult := Ping (sServer);
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
// executed in main thread after the async has finished
if Assigned (OnResult) then
OnResult (bResult);
end
)
);
end; { TOnTerminateTestForm.BackgroundPing }
procedure TOnTerminateTestForm.PingResult (const bResult: Boolean);
begin
PingMenu.Enabled := true;
LogMemo.Lines.Add ('Ping result = ' + BoolToStr (bResult, true));
end; { TOnTerminateTestForm.PingResult }
end.
Code source: Get a function result asynchronously in Delphi using Omni Thread Library

WASAPI AudioClient.GetMixFormat() returns AUDCLNT_E_NOT_INITIALIZED

I'm attempting to use AudioClient interface for the first time, without luck.
So far I managed to get the default AudioClient interface using the successfully MMDeviceEnumerator and MMDevice interfaces:
CoCreateInstance(
CLSID_MMDeviceEnumerator, nil,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
MMEnumerator);
MMEnumerator.GetDefaultAudioEndpoint(eRender,eConsole,MMDevice);
MMDevice.Activate(IID_IAudioClient, CLSCTX_ALL, nil, AudioClient);
(Result checking code not included here). This 3 call all returned no errors, and I've got a non-nil interface ptr in AudioClient variable.
My problem is when I try to get the mixing waveformat:
AudioClient.GetMixFormat(pwfx)
This returns the code 0x88890001 which is AUDCLNT_E_NOT_INITIALIZED. -> Of course not initialized as I only wanted to get what waveformat it likes first.
Looking up msdn tells that the AudioClient.GetMixFormat can be called before AudioClient.Initialization. Also the AUDCLNT_E_NOT_INITIALIZED is not on the list of possible return values. So I'm confused about what did I do wrong. GetMixFormat() doc -> http://msdn.microsoft.com/en-us/library/windows/desktop/dd370872(v=vs.85).aspx
Another weird thing is that when I call AudioClient.GetStreamLatency() it returns with S_OK and with a quasi-random value around 1000ms. But the documentation states that "This method requires prior initialization of the IAudioClient interface. All calls to this method will fail with the error AUDCLNT_E_NOT_INITIALIZED until the client initializes the audio stream by successfully calling the". Therefore I think I have a working AudioClient interface, I just can't understand why it doesn't work as the way documentation says.
(I'm using win7 64bit, Sound Blaster Live 5.1 with kx-project driver (DSound and classic windows MM sound works ok, but with a 100ms terrible long latency, that's the only reason why I'm going to use WASAPI on the win7)
Thank you in advance.
I actually found the bug. The definition of IAudioClient in MFPack is incorrect, the interface functions are in the wrong order. (I'd like to push this in some way some day, if I find the time for it, move to git etc.)
This is the correct ordering of methods in IAudioClient:
IAudioClient = interface(IUnknown)
['{1CB9AD4C-DBFA-4c32-B178-C2F568A703B2}']
function Initialize(ShareMode: AUDCLNT_SHAREMODE; StreamFlags: Dword; hnsBufferDuration: REFERENCE_TIME; hnsPeriodicity: REFERENCE_TIME; pFormat: PWaveFormatEx; AudioSessionGuid: LPCGUID): HResult; stdcall;
function GetBufferSize(out pNumBufferFrames: UINT32): HResult; stdcall;
function GetStreamLatency(out phnsLatency: REFERENCE_TIME): HResult; stdcall;
function GetCurrentPadding(out pNumPaddingFrames: UINT32): HResult; stdcall;
function IsFormatSupported(ShareMode: AUDCLNT_SHAREMODE; pFormat: PWaveFormatEx; out ppClosestMatch: PWaveFormatEx): HResult; stdcall;
function GetMixFormat(out ppDeviceFormat: PWaveFormatEx): HResult; stdcall;
function GetDevicePeriod(out phnsDefaultDevicePeriod: REFERENCE_TIME; phnsMinimumDevicePeriod: REFERENCE_TIME): HResult; stdcall;
function Start(): HResult; stdcall;
function Stop(): HResult; stdcall;
function Reset(): HResult; stdcall;
function SetEventHandle(const eventHandle: HANDLE): HResult; stdcall;
function GetService(const riid: TGUID; out ppv: Pointer): HResult; stdcall;
//The GetService method supports the following service interfaces: IAudioCaptureClient, IAudioClock, IAudioRenderClient,
//IAudioSessionControl, IAudioStreamVolume, IChannelAudioVolume, IMFTrustedOutput, ISimpleAudioVolume.
//Since Windows 7 the new interface indentifier IID_IMFTrustedOutput has been added, but is not implemented here.
end;
The function ReleaseBuffer is also wrong, this is the correct parameters:
IAudioRenderClient = interface(IUnknown)
['{F294ACFC-3146-4483-A7BF-ADDCA7C260E2}']
function GetBuffer(const NumFramesRequested: UINT; out ppData: PByte): HResult; stdcall;
function ReleaseBuffer(const NumFramesWritten: UINT32; const dwFlags: DWord): HResult; stdcall;
end;

Concat PChar with string in Delphi

I need to construct a string and send it via PostMessage, ie.
FileName := String_1 + String_2 + String_3;
PostMessage(FWndHandle, WM_BLA_BLA, NotifyData^.Action, LParam(FileName));
but something isn't working. Plus, FileName is a PChar. The code looks like this:
var
FileName : PChar;
Directory_Str : String;
AnotherString : String;
begin
// Get memory for filename and fill it with data
GetMem(FileName, NotifyData^.FileNameLength + SizeOf(WideChar));
Move(NotifyData^.FileName, Pointer(FileName)^, NotifyData^.FileNameLength);
PWord(Cardinal(FileName) + NotifyData^.FileNameLength)^ := 0;
// TODO: Contact string before sending message
// FileName := AnotherString + Directory_Str + FileName;
PostMessage(FWndHandle, WM_BLA_BLA, NotifyData^.Action, LParam(FileName));
...
end;
Now I need to do contact another string to the variable FileName before calling PostMessage, ie.
FileName := AnotherString + Directory_Str + FileName;
PostMessage(FWndHandle, WM_BLA_BLA, NotifyData^.Action, LParam(FileName));
This would work if FileName was a string, which is not the case here.
Anyone knows how to do that with PChar? I tried these methods, works sometimes but always something breaks at the end:
StrPCopy(FileName, FDirectory + String(FileName));
OR
FileName := PChar(AnotherString + Directory_Str + FileName);
You cannot easily use PostMessage with data passed by reference. The reason being that PostMessage executes asynchronously and you need to keep the memory you are passing alive until the message has been processed by its recipient. I guess that's what is behind your GetMem code.
Obviously this only works within the same process. And you will also find that Windows won't let you use PostMessage for any of its messages that receive pointers. For example, PostMessage with WM_SETTEXT always fails. You can only hope to do this using a user-defined message. And of course you'll need to deallocate the memory in the code that receives the message.
I'm going to assume that you are using a user defined message that does allow sending a string with PostMessage. In which case you already have the solution. Do the concatenation using string variables and then use the first block of code in your answer.
Although you can make it much cleaner like this:
function HeapAllocatedPChar(const Value: string): PChar;
var
bufferSize: Integer;
begin
bufferSize := (Length(Value)+1)*SizeOf(Char);
GetMem(Result, bufferSize);
Move(PChar(Value)^, Result^, bufferSize);
end;
procedure PostString(Window: HWND; Msg: UINT; wParam: WPARAM;
const Value: string);
var
P: PChar;
begin
P := HeapAllocatedPChar(Value);
if not PostMessage(Window, Msg, wParam, LPARAM(P)) then
FreeMem(P);
end;
And you can just call that procedure like this:
PostString(FWndHandle, WM_BLA_BLA, NotifyData^.Action, FDirectory + FileName);
Your current code fails because:
When you call StrPCopy you don't allocate any memory for the longer string.
When you write PChar(AnotherString + Directory_Str + FileName) you fall into the trap of that you were trying to avoid with GetMem. That's a local string which has been deallocated by the time the message is processed.
If you can find a way of solving your problem without using PostMessage to pass a string, that might be preferable to all this complexity.
See David's answer for reasons why your code fails.
I always define a class for distributing strings through PostMessage operations.
No risk of leaks and it's easy to expand with more information.
Type
TMyMessage = class
msg : String;
Constructor Create(aMessage : String);
end;
// Sending the message
var
myMsg : TMyMessage;
...
myMsg := TMyMessage.Create('A message');
if not PostMessage(someHandle,WM_something,WParam(myMsg),0)
then begin
myMsg.free;
... Take care of this condition if PostMessage fails !!
end;
// Receiving the message
procedure TSomeForm.GetMessage(var msg : TMessage);
var
aMsg : TMyMessage;
begin
...
aMsg := TMyMessage(msg.WParam);
try
...
// Do something with the message
finally
aMsg.Free;
end;
end;

Access to vcl component in thread! Delphi

So, my goal is start a function in another thread. Also i need access to other vcl components from new thread. Here is my code so far:
procedure TForm1.StartButtonClick(Sender: TObject);
var
thread1: integer;
id1: longword;
begin
thread1 := beginthread(nil,0,Addr(Tform1.fetchingdata),nil,0,id1);
closehandle(thread1);
end;
procedure TForm1.FetchingData;
var
...
begin
Idhttp1.IOHandler := IdSSLIOHandlerSocketOpenSSL1; //<- error
idhttp1.Request.ContentType := 'application/x-www-form-urlencoded';
my program hangs and i get error: Exception EAccessViolation in module my.exe at 00154E53. Access violation at address 00554E53 in module 'my.exe'. Read of address 00000398.
Thanks in advance.
The cause of the AV is that you pass the address of a TForm method to a function that expects a TThreadFunc (see the documentation of System.BeginThread()). Using Addr() like this is a good way to keep the compiler from pointing out your bugs.
What you would need to do instead is to write a wrapper function that has the correct signature, pass the form instance as the parameter, and call the method on the form from that function.
But don't go there, either write your code as a descendant of TThread, or (preferably) use a higher level wrapper like AsyncCalls or the Omni Thread Library. And make sure that you don't access VCL components in the main thread, create and free those that you need in your worker thread.
The VCL (Gui components) is only to be accessed from the main thread. Other threads need the main thread to access the VCL.
You could try the same thing with a regular TThread if you're using Delphi or Lazarus.
type
TSeparateThread = class(TThread)
private
protected
public
constructor Create(IfSuspend: Boolean);
proceedure Execute; override;
// variables to fill go here
// s : String;
// i : Integer;
// etc...
end;
constructor TSeparateThread.Create(IfSuspend: Boolean);
begin
inherited Create(IfSuspend);
end;
procedure TSeparateThread.Execute;
begin
// This is where you will do things with those variables and then pass them back.
YourMainUnitOrForm.PublicVariableOf := s[i];
// passes position 0 of s to PublicVariableOf in your Main Thread
end;
Calling the new Thread is done as follows:
with TSeparateThread.Create(true) do
begin
// This is where you fill those variables passed to the new Thread
s := 'from main program';
i := 0;
// etc...
Resume;
//Will Start the Execution of the New Thread with the variables filled.
end;

Resources