ShellExecute not working from IDE but works otherwise - delphi

I want to create and then open a txt file using the ShellExecute command.
I have used this code for years with Delphi 7 and it worked:
function Execute(CONST ExeName, Parameters: string): Boolean;
begin
Result:= ShellExecute(0, 'open', PChar(ExeName), PChar(Parameters), nil, SW_SHOWNORMAL)> 32;
end;
Now, I switched to Windows 7 and the code is not working anymore when it runs from IDE. Delphi shows the CPU window with the caption "CPU-Process unknown (2352)". I close the CU windows and everything works fine until I close the application, when Delphi shows the CPU window one more time.
If I run the app from outside IDE, it works fine.
Looks like the debugger has something to say to me, but I don't know what.

Sounds to me like you have the "debug spawned processes" option turned on. When that's enabled, the debugger interrupts the new process at the earliest possible time. Press the "run" button to let it continue running.
You can confirm this hypothesis the next time you debug your program. Compare the process ID (2352, in your example) with the list of processes shown by Task Manager. Which process in that list matches the process ID reported by the debugger?

This is not the answer for your question (I vote for Rob Kennedy & Chris Thornton), but you can write your routine in a more compact way:
function Executa(const ExeName, Parameters: string): Boolean;
begin
Result :=
(ShellExecute(0, 'open', PChar(ExeName), Pointer(Parameters), nil, SW_SHOWNORMAL) > 32);
end;
Note Pointer() instead of PChar() for 4th argument. This is a documented behaviour of PChar/Pointer casts (see help).

I had a problem yesterday with the debugger crashing my application, but running it outside the IDE it would run fine. I was using packages in my development.
I used process explorer to verify I found I was loading a copy from another location than expected. I had two copies of the same BPL floating around. Once I removed the one I was not compiling I was fine.
Applying that to this problem, I would check to make sure you don't have any copies of compiled code that includes: .DCU, .DCP, .BPL, .EXE around. Then I would also make sure you you can ctrl-click on "ShellExecute" to and see the declaration. You may have your library path setup in a way that it can't find the source.

Shot in the dark here, but try running the IDE as administrator, and then not as administrator. That may be a factor. Some users make a shortcut with the administrator option set, so that the auto-update runs successfully. So you may be running the IDE as admin, if you've done that.

Same by me , i solved it by replacing ShellExecute with following:
function TformMain.CreateProcessSimple(
sExecutableFilePath : string )
: string;
function GetExeByExtension(sExt : string) : string;
var
sExtDesc:string;
begin
with TRegistry.Create do
begin
try
RootKey:=HKEY_CLASSES_ROOT;
if OpenKeyReadOnly(sExt) then
begin
sExtDesc:=ReadString('') ;
CloseKey;
end;
if sExtDesc <>'' then
begin
if OpenKeyReadOnly(sExtDesc + '\Shell\Open\Command') then
begin
Result:= ReadString('') ;
end
end;
finally
Free;
end;
end;
end;
var
pi: TProcessInformation;
si: TStartupInfo;
fapp: string;
begin
fapp:=GetExeByExtension(ExtractFileExt(sExecutableFilePath));
FillMemory( #si, sizeof( si ), 0 );
si.cb := sizeof( si );
if Pos('%1',fApp)>0 then begin
sExecutableFilePath:=StringReplace(fapp,'%1',sExecutableFilePath,[rfReplaceAll]);
end else begin
sExecutableFilePath:=fApp+' "'+sExecutableFilePath+'"';
end;
CreateProcess(
Nil,
// path to the executable file:
PChar( sExecutableFilePath ),
Nil, Nil, False,
NORMAL_PRIORITY_CLASS, Nil, Nil,
si, pi );
// "after calling code" such as
// the code to wait until the
// process is done should go here
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
end;

ShellExecuteW solve my problems (XE2/Win7/32bit) with "debug spawned processes" option turned off
:)
mybe because strings and pchar are wide pointer from 2010

Related

Closing a CMD Window after launching an application through command line from Delphi

I am trying to do the following. It works but the cmd window waits for the acrobat.exe to finish executing before exiting. I have to use this method of launching because I intend to pass certain command line parameters in future.
cmdLineString := Format('/c ""%s" "%s""',['C:\Program Files (x86)\Adobe\Acrobat 11.0\Acrobat\Acrobat.exe', 'F:\Android-interview\Packt.Android.3.0.Application.Development.Cookbook.Jul.2011.ISBN.1849512949.pdf']);
ShellExecute(Handle, 'open', 'cmd.exe', PChar(CmdLineString), nil, SW_SHOWNORMAL);
There are a number of ways to improve this:
Don't use ShellExecute. It is tempting to do so because it is simple to call. However, it is not very flexible. Use CreateProcess instead.
If you must hide a console window, pass the CREATE_NO_WINDOW flag to CreateProcess.
That said, there is no point to use cmd here. You don't need to create a process that creates another process. Doing so actually makes it harder to pass on arguments. Create the Acrobat process directly. Cut out the middle man.
As answered by David and after following a few other questions regarding CreateProcess the solution code finally looks as shown below. Putting here for other beginners like me. Just think about what all things are possible with this piece of code! Thank you, Delphi.
procedure TForm.btnCMDLaunchClick(Sender: TObject);
var
commandLine: string;
si: TStartupInfo;
pi: TProcessInformation;
begin
commandLine := Format('%s %s',['C:\Program Files (x86)\Adobe\Acrobat 11.0\Acrobat\Acrobat.exe', 'F:\Android-interview\Packt.Android.3.0.Application.Development.Cookbook.Jul.2011.ISBN.1849512949.pdf']);
UniqueString(commandLine);
si := Default(TStartupInfo);
si.cb := sizeof(si);
if CreateProcess(
PChar(nil), //no module name (use command line)
PChar(commandLine), //Command Line
nil, //Process handle not inheritable
nil, //Thread handle not inheritable
False, //Don't inherit handles
0, //No creation flags
nil, //Use parent's environment block
PChar(nil), //Use parent's starting directory
si, //Startup Info
pi //Process Info
) then begin
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
end;
end;

displaySwitch.exe code replacement for windows (pre windows 7)

I'm writing an app I'd like to be backwardly compatible to some extent on XP, or at the very least windows vista.
EDIT FOR CLARITY: I need to be able to do what the first code snippet below does, but in XP. "Does anybody know the best approach to take under XP, given the functions aren't available in USER32.DLL.?"
My initial prototype code on windows 7 just called CreateProcess to start up displayswitch.exe, which is deployed with windows 7.
if you are not familiar with it, it's a handy little utility that is what gets invoked when you press the windows key and the letter P. you can read more about it here.
while this was adequate, i subsequently needed to sense the current state (eg internal vs external or extend vs clone), so i have now coded up a winapi solution that works well on windows 7 (and i presume 8). it involves making calls to SetDisplayConfig and QueryDisplayConfig in User32.DLL
The pertinent section of it is here (minus the many, many structures i had to hand craft in pascal code from the original klingon).
function getTopology : DISPLAYCONFIG_TOPOLOGY_ID ;
var NumPathArrayElements,
NumModeInfoArrayElements : UINT32;
var PathArrayElements_Size,
ModeInfoArrayElements_Size : UINT32;
error : Longint;
paths : PDISPLAYCONFIG_PATH_INFO_array;
info : PDISPLAYCONFIG_MODE_INFO_array;
begin
NumModeInfoArrayElements := 0;
Result := DISPLAYCONFIG_TOPOLOGY_EXTERNAL;
inc(result);
error := GetDisplayConfigBufferSizes(QDC_DATABASE_CURRENT,NumPathArrayElements,NumModeInfoArrayElements);
case error of
ERROR_SUCCESS :
begin
PathArrayElements_Size := sizeof(DISPLAYCONFIG_PATH_INFO) * NumPathArrayElements ;
ModeInfoArrayElements_Size := sizeof(DISPLAYCONFIG_MODE_INFO) * NumModeInfoArrayElements;
GetMem(paths,PathArrayElements_Size);
try
GetMem(info,ModeInfoArrayElements_Size );
try
error := QueryDisplayConfig(QDC_DATABASE_CURRENT,NumPathArrayElements, paths,NumModeInfoArrayElements, info,result);
case error of
ERROR_SUCCESS :;
else
Result := DISPLAYCONFIG_TOPOLOGY_EXTERNAL;
inc(result);
end;
finally
FreeMem(info,ModeInfoArrayElements_Size );
end;
finally
FreeMem(paths,PathArrayElements_Size);
end;
end;
end;
end;
function setTopology ( top : DISPLAYCONFIG_TOPOLOGY_ID) : boolean;
var flags : dword;
begin
result := false;
flags := DecodeDISPLAYCONFIG_TOPOLOGY_ID_SDC(top);
if flags <> 0 then
begin
result := SetDisplayConfig(0,nil,0,nil,SDC_APPLY or flags) = ERROR_SUCCESS;
end;
end;
Since these functions don't exist in XP (as far as I know), I am looking for a stable way of achieving a similar thing in XP. whilst i am coding in Delphi, it's not necessary that the solution be presented as such. i am quite happy to just look at how it's done, or read a description of the appropriate steps, and implement it myself.
(removed full listing as it was confusing the issue as it did not appear like a question)

Why doesn't my command run like I expected when I use ShellExecute?

I am trying to dump a PDF to text using a command line utility (It works with tests from dos command line) from my Delphi code.
Here is my code
if fileexists(ExtractFilePath(Application.ExeName) + 'pdftotext.exe') then
begin
ShellExecute(H,'open', 'pdftotext.exe', PWideChar(fFileName), nil, SW_SHOWNORMAL);
if fileExists(changeFileExt(fFileName, '.txt')) then
Lines.LoadFromFile(changeFileExt(fFileName, '.txt'))
else
ShowMessage('File Not found');
end;
When placing breakpoints in code and stepping through, it makes it to the
if fileExists(changeFileExt(fFileName, '.txt')) then
line but returns false, so the Shellexecute was called but no file was ever dumped
What have I done wrong?
ShellExecute doesn't wait for the invoked program to finish running. You're probably checking for the file too soon. The file simply hasn't been created yet.
Run the program and wait for it to terminate before you check for the output file. ShellExecute doesn't return enough information for you to do that, so you should try CreateProcess instead. There are several examples of how to do that. Try this:
How can I wait for a command-line program to finish?
It turns out, that adding the fill path to the execuatble made it work just fine
uses
Forms, ShellAPI, SysConst, SysUtils;
procedure Pdf2Text(const fFileName: string; const Lines: TStrings);
var
H: HWND;
PdfToTextPathName: string;
ReturnValue: Integer;
TxtFileName: string;
begin
H := 0;
PdfToTextPathName := ExtractFilePath(Application.ExeName) + 'pdftotext.exe'; // full path
if FileExists(PdfToTextPathName) then
begin
ReturnValue := ShellExecute(0,'open', PWideChar(PdfToTextPathName), PWideChar(fFileName), nil, SW_SHOWNORMAL);
if ReturnValue <= 32 then
RaiseLastOsError();
// note: the code below this line will crash when pdftotext.exe does not finish soon enough; you should actually wait for pdftotext.exe completion
TxtFileName := ChangeFileExt(fFileName, '.txt');
if FileExists(TxtFileName) then
Lines.LoadFromFile(TxtFileName)
else
raise EFileNotFoundException.CreateRes(#SFileNotFound);
end;
end;
Edit: Some code cleanup helps big time to catch errors early on, especially when testing a proof of concept.

Delphi7 CustomImageList problem

I've run into the following problem:
My Delphi7 program runs smoothly on most computers running WinXP/Vista/7 BUT on some older Windows XP installs (only a few) I'm getting the following problem:
I have a system image list, and I'm adding my own icons to a copy of the system image list. Upon adding my icons I get an "Invalid image size." EInvalidOperation error.
Here is the code in question:
function GetSystemLargeIconsList: TCustomImageList;
// This gets the system image list.
var
SysIL: HImageList;
SFI: TSHFileInfo;
MyImages: TCustomImageList;
begin
SysIL := SHGetFileInfo('', 0, SFI, SizeOf(SFI),
SHGFI_SYSICONINDEX or SHGFI_LARGEICON);
if SysIL <> 0 then begin
MyImages:=TCustomImageList.Create(nil);
// Assign the system list to the component
MyImages.Handle := SysIL;
// The following prevents the image list handle from being
// destroyed when the component is.
MyImages.ShareImages := TRUE;
Result:=MyImages;
end;
end;
var
DocumentImgList: TCustomImageList;
IconToAdd: TIcon;
begin
DocumentImgList:=GetSystemLargeIconsList;
Documents.LargeImages:=DocumentImgList;
Documents.SmallImages:=DocumentImgList;
IconToAdd:=TIcon.Create;
DocumentListIcons.GetIcon(0, IconToAdd);
DocumentImgList.AddIcon(IconToAdd); ----> this is the line of the exception
To make the problem worse, I'm using the TPngImageList component, but according to the code, it just seems to call the standard Delphi function:
if TObject(Self) is TPngImageList
then if Image = nil
...
else begin
Patch := FindMethodPatch('AddIcon');
if Patch <> nil
then begin
Patch.BeginInvokeOldMethod;
try
Result := TCustomImageList(Self).AddIcon(Image); ----> this is where the exception happens
finally
Patch.FinishInvokeOldMethod;
end;
end
else Result := -1;
end;
I've recently found out that on one of the computers that have this problem, either uxtheme.dll or explorer.exe has been patched with some Windows-skinning program.
So I suppose that somebody or a program is hacking the system image list in a way that is making my Delphi program crash.
Any ideas on how to fix this?
Thanks!
One thing you could try would be to load your icon into a separate tBitmap, then resize it before adding it into the image list.

how to open additional files into an already running application

I am writing a MDI Text Editor and I was wondering how can I open all text files with my app. (If I associate te *.txt to my app I want that each time someone double-clicks on a txt file to open it in my app, in a new child window)
thanks
The solution to this is also the solution to not allowing more than one application to run at the same time. What you want to do is first detect that the program is already running, then pass a parameter to the running application and shut down.
There are several methods to determine if your application is already running. Once you pick one that fits your programming preferences, the next step is to feed the file to open to your running program. This can be done via named pipes, messages (although messages do fail on Vista/Win7 if your app is running in another security context), or any other method of IPC.
I Currently have the following implementation for this :
The .dpr file
var
PrevWindow : HWND;
S : string;
CData : TCopyDataStruct;
begin
PrevWindow := 0;
if OpenMutex(MUTEX_ALL_ACCESS, False, 'YourUniqueStringHere') <> 0 then
begin
PrevWindow:=FindWindow('TYourMainFormClassName', nil);
if IsWindow(PrevWindow) then
begin
SendMessage(PrevWindow, WM_SYSCOMMAND, SC_RESTORE, 0);
BringWindowToTop(PrevWindow);
SetForegroundWindow(PrevWindow);
if FileExists(ParamStr(1)) then
begin
S:=ParamStr(1);
CData.dwData:=0;
CData.lpData:=PChar(S);
CData.cbData:=1+Length(S);
SendMessage(PrevWindow, WM_COPYDATA, 0, DWORD(#CData) );
end;
end;
end
else
CreateMutex(nil, False, 'YourUniqueStringHere');
in the main unit we process the WM_COPYDATA message :
we declare the message handler
procedure ReceiveData_Handler ( var msg : TWMCopyData ) ; message WM_COPYDATA;
procedure TForm1.ReceiveData_Handler(var msg: TWMCopyData);
begin
// Your file name is in the msg.CopyDataStruct.lpData
// Cast it to PChar();
end;
Hope it works for you.
Check out the Windows DDE documentation. I modify the DDEExec options in the registry, so the shell correctly directs the opened file to my existing application instance. The following code makes the registry changes necessary. Replace "AppName" with your application name (and remove the brackets).
// add the ddeexec key
if not reg.OpenKey( '\Software\Classes\<AppName>.file\shell\open\ddeexec', true ) then
raise Exception.Create( 'Error setting ddeexec key' );
try
reg.WriteString( '', 'FileOpen("""%1""")' );
finally
reg.CloseKey;
end;
// modify the command key to not include the parameter, as we don't use it
if not reg.OpenKey( '\Software\Classes\<AppName>.file\shell\Open\command', true ) then
raise Exception.Create( 'Error opening command key.' );
try
strTemp := reg.ReadString( '' );
strTemp := StringReplace( strTemp, '"%1"', '', [] );
reg.WriteString( '', strTemp );
finally
reg.CloseKey;
end;
I don't know the version of Delphi that you're using, but in Delphi 7 at the examples folder you will see a MDI Text Editor example.

Resources