aehimself 399 Posted December 11, 2022 (edited) After solving some mysteries in DDE I decided to extend my Delphi instance helper with a new method: to launch a new Delphi instance of a selected version. The theory is simple: launch bds.exe and return the TDelphiInstance object only when the IDE is available (this was removed to further simplify the calls happening). While the new process starts, I'm getting Access violation at address 00000000 in module 'Project3.exe'. Execution of address 00000000 upon: - Calling ShowMessage (WinApi.CommCtl : 13107, TaskDialogIndirect) Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton, pfVerificationFlagChecked) - Application.ProcessMessages loop (Vcl.Forms : 11460, TApplication.ProcessMessage) DispatchMessageW(Msg) - Closing the instance which was created by CreateProcess (error raises in ProcessMessages, just like above) From the third appearance I believe I'm not using CreateProcess correctly (in theory, after closing all handles my application should have nothing to do with the instance created) but if I move the code out of TBorlandDelphiVersion to the project itself, it just works. Launching the exe is performed as follows: Function TBorlandDelphiVersion.NewDelphiInstance: TDelphiInstance; Var StartInfo : TStartupInfo; ProcInfo : TProcessInformation; ec: Cardinal; Begin Result := nil; FillChar(StartInfo, SizeOf(TStartupInfo), #0); StartInfo.cb := SizeOf(TStartupInfo); FillChar(ProcInfo, SizeOf(TProcessInformation), #0); If Not CreateProcess(PChar(_bdspath), nil, nil, nil, False, CREATE_NEW_PROCESS_GROUP, nil, nil, StartInfo, ProcInfo) Then RaiseLastOSError; Try // If Not GetExitCodeProcess(procinfo.hProcess, ec) Then // RaiseLastOSError; // // WaitForInputIdle(procInfo.hProcess, INFINITE); Repeat If Not Assigned(Result) Then Begin Self.RefreshInstances; Result := Self.InstanceByPID(procinfo.dwProcessId); End; Until Assigned(Result);// And ((Result.IDEHWND <> 0) Or Result.FindIdeWindow) And Not Result.IsIDEBusy; // Normally here, removed for simplicity Finally CloseHandle(ProcInfo.hThread); CloseHandle(ProcInfo.hProcess); End; End; I attached the full source of said class, you can reproduce the issue by... Uses AE.DelphiVersions; procedure TForm3.Button1Click(Sender: TObject); begin ShowMessage(ni.ToString); End; procedure TForm3.Button2Click(Sender: TObject); Var dv: TDelphiVersions; c: Cardinal; begin dv := TDelphiVersions.Create(nil); Try c := dv.LatestVersion.NewDelphiInstance.PID; // Self.Caption := c.ToString; // This works, no exception is raised ShowMessage(c.ToString); Finally FreeAndNil(dv); End; end; function TForm3.ni: cardinal; Var dv: TDelphiVersions; begin dv := TDelphiVersions.Create(nil); Try Result := dv.LatestVersion.NewDelphiInstance.PID; Finally // FreeAndNil(dv); // If you do not free this up, exception is raised in ShowMessage. End; end; MadExcept reports no buffer under- or overruns, no memory, handle or any other leaks are reported by DeLeaker. I never really used CreateProcess, and never actually managed to produce an execute at address AV up until this point. Can anyone point me to the right direction? P.s.: Using Delphi 11.2, compiled app is 32 bit, cannot check if the issue will appear anywhere else. AE.DelphiVersions.zip Edited December 11, 2022 by aehimself Share this post Link to post
David Heffernan 2353 Posted December 11, 2022 First argument of CreateProcess must be writeable? Is it? Share this post Link to post
aehimself 399 Posted December 11, 2022 18 minutes ago, David Heffernan said: First argument of CreateProcess must be writeable? Is it? I was unaware of this, and even MSDN specifies it as IN only. Do you maybe have some references I can look into? Changing it to pc: PChar; Begin [...] GetMem(pc, Length(_bdspath) + 1); Try StrPCopy(pc, _bdspath); If Not CreateProcess(pc, nil, nil, nil, False, CREATE_NEW_PROCESS_GROUP, nil, nil, StartInfo, ProcInfo) Then RaiseLastOSError; Finally FreeMem(pc); End; still throws the AV. I am really baffled by the nature of this issue. Popping up at window messages, and being "execution at address" I'd say it's like a memory corruption. I have doubts though as commenting out the two CloseHandle lines will delay / get rid of these. That clearly points to improper use of CreateProcess... Share this post Link to post
David Heffernan 2353 Posted December 11, 2022 The documentation says the first arg is LPCWSTR, pointer to const array. If you want to make a string writeable use UniqueString. Share this post Link to post
aehimself 399 Posted December 11, 2022 Calling UniqueString(_bdspath) before the CreateProcess call still produces an error. Maybe it's even unnecessary...? https://www.oreilly.com/library/view/delphi-in-a/1565926595/re354.html "If you cast a string to PChar, Delphi also ensures that the PChar points to a unique instance of the string. If you modify the string through the PChar pointer, the other references to the original string are safe." I don't know, I am in completely uncharted territory here. Never had to think more of PChar castings than if a method needed it instead of a String. Share this post Link to post
David Heffernan 2353 Posted December 11, 2022 8 minutes ago, aehimself said: https://www.oreilly.com/library/view/delphi-in-a/1565926595/re354.html "If you cast a string to PChar, Delphi also ensures that the PChar points to a unique instance of the string. If you modify the string through the PChar pointer, the other references to the original string are safe." I don't think that's true. You should use UniqueString. But it sounds like this isn't the cause of your problem. 1 Share this post Link to post
Remy Lebeau 1436 Posted December 11, 2022 1 hour ago, David Heffernan said: First argument of CreateProcess must be writeable? Is it? Incorrect, it is the 2nd argument that must be writable. And only in the Unicode version. Share this post Link to post
Remy Lebeau 1436 Posted December 11, 2022 2 hours ago, aehimself said: While the new process starts, I'm getting Access violation at address 00000000 in module 'Project3.exe'. Execution of address 00000000 upon: That means you are trying to call a function through a nil function pointer. 2 hours ago, aehimself said: - Calling ShowMessage (WinApi.CommCtl : 13107, TaskDialogIndirect) Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton, pfVerificationFlagChecked) Did you check to see whether _TaskDialogIndirect is nil or not? 1 Share this post Link to post
David Heffernan 2353 Posted December 11, 2022 (edited) 13 minutes ago, Remy Lebeau said: Incorrect, it is the 2nd argument that must be writable. And only in the Unicode version. Oh. Sorry. My mistake. Only in the Unicode version is an implementation detail though. I wouldn't rely on it. Edited December 11, 2022 by David Heffernan Share this post Link to post
aehimself 399 Posted December 11, 2022 25 minutes ago, Remy Lebeau said: That means you are trying to call a function through a nil function pointer. Did you check to see whether _TaskDialogIndirect is nil or not? I only called ShowMessage, which requires nothing but a string to display. I checked all parameters and confirmed that pszContent indeed contains the PID I'm about to display, but never thought of _TaskDialogIndirect being nil... it's in Delphi VCL after all. I just checked and yes it seems, it does point to a valid memory address: Share this post Link to post
Remy Lebeau 1436 Posted December 12, 2022 2 hours ago, David Heffernan said: Only in the Unicode version is an implementation detail though. I wouldn't rely on it. It is not an implementation detail, it is documented behavior: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw Quote The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation. Share this post Link to post
Remy Lebeau 1436 Posted December 12, 2022 2 hours ago, aehimself said: I just checked and yes it seems, it does point to a valid memory address: Using the Addr() intrinsic in that way will give you the memory address of the _TaskDialogIndirect variable itself, not the memory address that the variable holds. Try inspecting just _TaskDialogIndirect by itself, or at least cast it to a Pointer. 1 Share this post Link to post
aehimself 399 Posted December 12, 2022 5 hours ago, Remy Lebeau said: Using the Addr() intrinsic in that way will give you the memory address of the _TaskDialogIndirect variable itself, not the memory address that the variable holds. Try inspecting just _TaskDialogIndirect by itself, or at least cast it to a Pointer. Unfortunately (?) yeah, it has a valid address: There is also a check around this block in CommCtrl, if the method is not found we wouldn't even come to this block: if ComCtl32DLL <> 0 then begin @_TaskDialogIndirect := GetProcAddress(ComCtl32DLL, 'TaskDialogIndirect'); if Assigned(_TaskDialogIndirect) then Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton, pfVerificationFlagChecked) // Exception is raised here end; Share this post Link to post
David Heffernan 2353 Posted December 12, 2022 6 hours ago, Remy Lebeau said: It is not an implementation detail, it is documented behavior: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw Oh dear, I'm not having a good day. I think that obce upon a time this wasn't documented as such, and I've not kept up. But I could be wrong about that too! Share this post Link to post
aehimself 399 Posted December 12, 2022 As it turns out, the AV isn't caused by CreateProcess or anything related to it. The reason I wasn't able to reproduce it in a small bench application is I have a TDelphiDDEManager class created, which is however unused in the sample, but it calls DdeInitializeW. In the moment I comment that line out (or won't create the DDEManager instance) the AV disappears, PID is shown correctly, everything works like a charm. Question is, is it because of improper parameterization (like in UnpackDDElParam and FreeDDElParam) or I'm calling that wrong... Anyway, getting closer 🙂 Share this post Link to post