Why does Delphi TOpenDialog fail to open in the Initial Directory? - delphi

I am using TOpenDialog (in Delphi 10.4) to show the user the PDF files I have installed for them in their Documents folder. In that folder, I have created a folder MyFolder10.2 and copied the PDF files there.
The code is simple and has worked in the past, and even now it still works on my older slower Win10 machine. But on my newer faster Win10 computer, it only works SOME of the time. When it does not work, a file dialog is opened but in some other directory (not sure where that comes from), and it does not filter the file type (.pdf) that was set in the TOpenDialog component.
Any way to track down this mystery?
docPath:= GetEnvironmentVariable('USERPROFILE') + '\Documents\MyFolder10.2\';
OpenDocsDlg.InitialDir := docPath;
OpenDocsDlg.Execute;

Prior to Vista, TOpenDialog is a wrapper for the GetOpenFileName() API, where TOpenDialog.InitialDir maps to the OPENFILENAME.lpstrInitialDir field.
On Vista and later, TOpenDialog (usually, depending on configuration) wraps the IFileDialog/IFileOpenDialog API instead, where TOpenDialog.InitialDir maps to the IFileDialog.SetFolder() method (not to IFolderDialog.SetDefaultFolder(), as one would expect).
Per the OPENFILENAME documentation:
lpstrInitialDir
Type: LPCTSTR
The initial directory. The algorithm for selecting the initial directory varies on different platforms.
Windows 7:
If lpstrInitialDir [TOpenDialog.InitialDir] has the same value as was passed the first time the application used an Open or Save As dialog box, the path most recently selected by the user is used as the initial directory.
Otherwise, if lpstrFile [TOpenDialog.FileName] contains a path, that path is the initial directory.
Otherwise, if lpstrInitialDir is not NULL [TOpenDialog.InitialDir is not empty], it specifies the initial directory.
If lpstrInitialDir is NULL [TOpenDialog.InitialDir is empty] and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
Windows 2000/XP/Vista:
If lpstrFile [TOpenDialog.FileName] contains a path, that path is the initial directory.
Otherwise, lpstrInitialDir [TOpenDialog.InitialDir] specifies the initial directory.
Otherwise, if the application has used an Open or Save As dialog box in the past, the path most recently used is selected as the initial directory. However, if an application is not run for a long time, its saved selected path is discarded.
If lpstrInitialDir is NULL [TOpenDialog.InitialDir is empty] and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
And per the Common Item Dialog documentation:
Controlling the Default Folder
Almost any folder in the Shell namespace can be used as the default folder for the dialog (the folder presented when the user chooses to open or save a file). Call IFileDialog::SetDefaultFolder prior to calling Show [TOpenDialog.Execute()] to do so.
The default folder is the folder in which the dialog starts the first time a user opens it from your application. After that, the dialog will open in the last folder a user opened or the last folder they used to save an item. See State Persistence for more details.
You can force the dialog to always show the same folder when it opens, regardless of previous user action, by calling IFileDialog::SetFolder [TOpenDialog.InitialDir]. However, we do not recommended doing this. If you call SetFolder before you display the dialog box, the most recent location that the user saved to or opened from is not shown. Unless there is a very specific reason for this behavior, it is not a good or expected user experience and should be avoided. In almost all instances, IFileDialog::SetDefaultFolder is the better method.
When saving a document for the first time in the Save dialog, you should follow the same guidelines in determining the initial folder as you did in the Open dialog. If the user is editing a previously existing document, open the dialog in the folder where that document is stored, and populate the edit box with that document's name. Call IFileSaveDialog::SetSaveAsItem() with the current item prior to calling Show [TOpenDialog.Execute()].
Neither TOpenDialog nor TFileOpenDialog have properties that map to the IFileDialog.SetDefaultFolder() or IFileDialog.SetSaveAsItem() methods. However, the TFileOpenDialog.Dialog property does give you access to the underlying IFileDialog, so you can manually call these methods, such as in the TFileOpenDialog.OnExecute event.

The detailed answer by #Remy Lebeau triggered me to try again to fix a problematic common scenario I've not managed to fix before, despite numerous attempts:
I have an image edit VCL application with a TFileOpenDialog and a TFileSaveDialog that is often used for editing a bunch of screenshots one by one on a connected Android device which are saved on a desktop computer.
The problem was that the DefaultFolder for the FileOpenDialog didn't stick to the opened Android folder after a save on the computer. That is now fixed to my liking, and the solution might be of interest to someone who needs to play around with the dialogs.
The test code:
unit Main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Winapi.ShlObj, Winapi.KnownFolders, Winapi.ActiveX;
type
TMainForm = class(TForm)
FileOpenDialog1: TFileOpenDialog;
FileSaveDialog1: TFileSaveDialog;
OpenButton: TButton;
SaveButton: TButton;
Memo1: TMemo;
procedure FileOpenDialog1Execute(Sender: TObject);
procedure FileSaveDialog1Execute(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure OpenButtonClick(Sender: TObject);
procedure SaveButtonClick(Sender: TObject);
private
FOpenShellItem: IShellItem;
FSaveShellItem: IShellItem;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
function GetItemName(ShellItem: IShellItem; const Flags: Cardinal): string;
var
pszItemName: LPCWSTR;
begin
Result := '';
if ShellItem.GetDisplayName(Flags, pszItemName) = S_OK then
begin
Result := pszItemName;
CoTaskMemFree(pszItemName);
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
pch: PChar;
begin
DesktopFont := True;
if SHGetKnownFolderPath(FOLDERID_Pictures, 0, 0, pch) = S_OK then
begin
FileOpenDialog1.DefaultFolder := pch;
FileSaveDialog1.DefaultFolder := pch;
CoTaskMemFree(pch);
end;
end;
procedure TMainForm.OpenButtonClick(Sender: TObject);
var
ShellItem: IShellItem;
ParentItem: IShellItem;
begin
if FileOpenDialog1.Execute(Handle) then
begin
if fdoAllowMultiSelect in FileOpenDialog1.Options then
FileOpenDialog1.ShellItems.GetItemAt(0, ShellItem)
else
ShellItem := FileOpenDialog1.ShellItem;
if ShellItem.GetParent(ParentItem) = S_OK then
FOpenShellItem := ParentItem;
Memo1.Lines.Add('Opened');
Memo1.Lines.Add('ItemParsingName: ' +
GetItemName(ShellItem, SIGDN_DESKTOPABSOLUTEPARSING));
Memo1.Lines.Add('ItemNormalName: ' +
GetItemName(ShellItem, SIGDN_NORMALDISPLAY));
Memo1.Lines.Add('ParentItemParsingName: ' +
GetItemName(ParentItem, SIGDN_DESKTOPABSOLUTEPARSING));
Memo1.Lines.Add('ParentItemNormalame: ' +
GetItemName(ParentItem, SIGDN_NORMALDISPLAY));
Memo1.Lines.Add('----------');
end;
end;
procedure TMainForm.SaveButtonClick(Sender: TObject);
var
ParentItem: IShellItem;
begin
if FileSaveDialog1.Execute(Handle) then
begin
if FileSaveDialog1.ShellItem.GetParent(ParentItem) = S_OK then
FSaveShellItem := FileSaveDialog1.ShellItem;
Memo1.Lines.Add('Saved');
Memo1.Lines.Add('ItemParsingName: ' +
GetItemName(FileSaveDialog1.ShellItem, SIGDN_DESKTOPABSOLUTEPARSING));
Memo1.Lines.Add('ItemNormalName: ' +
GetItemName(FileSaveDialog1.ShellItem, SIGDN_NORMALDISPLAY));
Memo1.Lines.Add('ParentItemParsingName: ' +
GetItemName(ParentItem, SIGDN_DESKTOPABSOLUTEPARSING));
Memo1.Lines.Add('ParentItemNormalName: ' +
GetItemName(ParentItem, SIGDN_NORMALDISPLAY));
Memo1.Lines.Add('----------');
end;
end;
procedure TMainForm.FileOpenDialog1Execute(Sender: TObject);
begin
if Assigned(FOpenShellItem) then
FileOpenDialog1.Dialog.SetFolder(FOpenShellItem);
end;
procedure TMainForm.FileSaveDialog1Execute(Sender: TObject);
begin
//Note the IFileSaveDialog typecast
if Assigned(FSaveShellItem) then
IFileSaveDialog(FileSaveDialog1.Dialog).SetSaveAsItem(FSaveShellItem);
end;
end.

Related

How to close PDF document that is used in Acrobat Reader ActiveX component from Delphi?

I have importated Acrobate Reader ActiveX component in my Delphi application and loaded PDF document with code:
var AA: TAcroPDF;
function GetFilePath(AFileName: String): String;
begin
Result:=IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName))+AFileName;
end;
procedure TMainForm.Open2BtnClick(Sender: TObject);
var FPath: String;
begin
FPath:=GetFilePath('test.pdf');
if not FileExists(FPath) then raise Exception.Create('No file');
AA.LoadFile(FPath);
end;
But I can not unload PDF file from Acrobat ActiveX component. My use case requires that component unloads the file and shows gray rectangle upon the request from user. I have tried several code lines:
AA.Src:='';
AA.LoadFile('');
But file stays loaded all the time. I can successfully call the load of another file, but I can not find the way to unload the file.
I am using Delphi 2009, but I guess that my question applies to any version.
Question update and suggestion of hack as a solution:
I managed to adapt the code from https://stackoverflow.com/a/37889375/1375882 as for the close:
procedure TMainForm.Close1BtnClick(Sender: TObject);
var
Ref : Integer;
begin
if Assigned(AA) then begin
Ref:=AA.ControlInterface._AddRef;
AA.Src:='';
AA.Free;
AA:=Nil;
end;
end;
And accordingly I am checking if Assigned(AA) in FormClose as well. This works perfectly for me as far as I can see. But this seems to be no clean solution that was intended by Acrobat Reader?
Bad consequences of the suggested hack/solution:
No dark-gray rectangle remains on the form after onloading Acrobat Reader component in the suggested way and the subsequent call of anothes LoadFile(...) fails with AV, of course.

How to create a folder and a txt file in Delphi

How do I create a new folder in the internal storage (?) of Android (I want say, the main folder that has all the subfolders ... Whatsapp, DCIM, pictures, Ringtones, Alarms ..) and create a new .txt file inside in this folder.
I want to create a .txt file and I need the user to plug the USB cable into their computer, access the device, enter the folder my application creates, and copy this file to their desktop.
I tried this code to create the file:
procedure TF_start.Button2Click(Sender: TObject);
var
output_text: string;
arquivo: TextFile;
begin
output_text := 'test';
TFile.WriteAllText(TPath.Combine(TPath.GetDocumentsPath, 'test.txt'), 'content');
ReWrite(arquivo);
WriteLn(arquivo, output_text);
CloseFile(arquivo);
end;
But it does not work.
To get the internal storage(?) path, I found this code:
P := '/storage/';
if (FindFirst(P + '*', faAnyFile, Sr) = 0) then
repeat
Memo1.Lines.Add(Sr.Name);
until (FindNext(Sr) <> 0);
FindClose(Sr);
But I can't understand how it works, so I can't even use it.
I also found this link, but I didn't find any function that returns me the "general" directory path I want to create a folder.
The functions System.IOUtils.TPath.GetHomePath(), and System.IOUtils.TPath.GetDocumentsPath() do not return me the correct path.
System.SysUtils.GetHomePath() return -> /data/user/0/com.embarcadero.app/cache
System.IOUtils.TPath.GetDocumentsPath() return -> /data/com.embarcadero.app-1/lib/arm
#edit
Using the #Remy Lebeau code and this code I managed to get to this point.
The problem is that the code to update the directory with the files does nothing
Use System.IOUtils, Androidapi.Helpers, Androidapi.Jni.Media, Androidapi.JNI.JavaTypes, Androidapi.JNI.GraphicsContentViewText;
//Button
procedure TF_start.Button2Click(Sender: TObject);
var
path_file output_text: string;
begin
path_file := TPath.Combine(System.IOUtils.TPath.GetSharedDownloadsPath, 'Folder_app');
output_text := 'test';
if not TDirectory.Exists(path_file) then
TDirectory.CreateDirectory(path_file);
try
TFile.WriteAllText(TPath.Combine(path_file, Nome_Arquivo), Arquivo_saida);
except
ShowMessage('An error occurred while saving the file.');
end;
end;
Another button:
procedure TF_corrida.BTNfinalize_appClick(Sender: TObject);
var
c: Integer;
JMediaScannerCon: Androidapi.Jni.Media.JMediaScannerConnection;
JMediaScannerCon_Client: Androidapi.Jni.Media.JMediaScannerConnection_MediaScannerConnectionClient;
begin
JMediaScannerCon:=TJMediaScannerConnection.JavaClass.init(TAndroidHelper.Context, JMediaScannerCon_Client);
JMediaScannerCon.connect;
c:=0;
while not JMediaScannerCon.isConnected do begin
Sleep(100);
inc(c);
if (c>20) then break;
end;
if (JMediaScannerCon.isConnected) then begin
JMediaScannerCon.scanFile(StringToJString(path_file), nil);
JMediaScannerCon.disconnect;
end;
end;
PS This warning had appeared:
[DCC Warning] u_corrida.pas(682): W1000 Symbol 'SharedActivityContext'
is deprecated: 'Use TAndroidHelper.Context'
So I changed the code
Note: I also tried replacing "path_file" with "System.IOUtils.TPath.GetSharedDownloadsPath", but to no avail too
This question has already been answered, the other question (index files and folders) has been moved to: How to index a created file in Android sdcard Delphi
You don't actually want "internal storage", that is private to your app and not even the user can access it (without root access to the device). You want "external storage" instead, so the user (and other apps) can access it.
Per Save files on device storage in Android's documentation:
Internal storage is best when you want to be sure that neither the user nor other apps can access your files.
External storage is the best place for files that don't require access restrictions and for files that you want to share with other apps or allow the user to access with a computer.
Use one of the TPath.GetShared...() methods to get an "external storage" path, such as TPath.GetSharedDocumentsPath(). And make sure your app has the WRITE_EXTERNAL_STORAGE permission enabled.
Also, note that TFile.WriteAllText() will not create a missing folder (in fact, it will raise an EDirectoryNotFoundException). You have to create the folder yourself first, such as with TDirectory.CreateDirectory() or SysUtils.ForceDirectories(). TPath.Combine() simply concatenates the input strings together, it does not create the actual folder.
Try this:
procedure TF_start.Button2Click(Sender: TObject);
var
path, output_text: string;
begin
output_text := 'test';
path := TPath.Combine(TPath.GetSharedDocumentsPath, 'myfolder');
if not TDirectory.Exists(path) then
TDirectory.CreateDirectory(path);
TFile.WriteAllText(TPath.Combine(path, 'test.txt'), output_text);
end;

Error when changing TImage picture on click event

I have the following basic code :
procedure TForm4.shrek1Click(Sender: TObject);
begin
shrek1.Picture.LoadFromFile('donkey.jpeg');
end;
Where shrek1 is a TImage, and donkey.jpeg is the image I want shrek1 to load when clicked.
donkey.jpeg is located in the same directory of literally every other related project file, yet when I attempt to run the code I get an error:
Exception class EFOpenError with message 'Cannot open file "\(removed directory for privacy)\donkey.jpeg". The system cannot find the file specified
What am I doing wrong?
Always use absolute paths. Relative paths are relative to the calling processe's Current Working Directory, which can (and usually does) change value during the process's lifetime, and is not always what you expect.
If the JPG file is in the same folder as the your EXE, you can do this instead:
var
AppPath: string;
procedure TForm4.shrek1Click(Sender: TObject);
var
FileName: string;
begin
FileName := AppPath+'donkey.jpeg'; // <-- make sure this path is accurate!
shrek1.Picture.LoadFromFile(FileName);
end;
initialization
AppPath := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName));

Delphi : How to set sound/image locations?

How do I effectively set sound/image locations for example :
procedure TForm1.Button1Click(Sender: TObject); begin
begin
PlaySound('C:\Users\username\Desktop\project\sfx\Sounds\ding.wav', 0, SND_ASYNC);
end;
To something like :
procedure TForm1.Button1Click(Sender: TObject); begin
begin
PlaySound('ding.wav', 0, SND_ASYNC);
end;
Everytime I move The folder that contains the the project the sounds seem to not work anymore because the directory changes.
You should set / state the locations relative to the project file. For example:
var lApplicationExecutablePath: String;
lApplicationExecutablePath := ExtractFilePath(ParamStr(0));
...
var lSoundFile: String;
lSoundFile := TPath.Combine(FolderName, 'sfx\ding.wav');
This way you will have all needed files side by side no matter where the application is installed.
If you supply a relative path it is interpreted as relative to the process working directory. As a general rule, in a GUI application you should always supply absolute paths since the process working directory may be ill-defined.
You don't need to hard code the file name. If you know the name of the file, and the folder that it is in, then you can simply combine the name of file folder with the name of the file, and obtain the full path to the file. Pass that to PlaySound.
uses
IOUtils;
....
PlaySound(PChar(TPath.Combine(FolderName, FileName)), 0, SND_ASYNC);

Preventing multiple instances - but also handle the command line parameters?

I am handling from my Application associated extension files from Windows. So when you double click a file from Windows it will execute my program, and I handle the file from there, something like:
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 0 to ParamCount -1 do
begin
if SameText(ExtractFileExt(ParamStr(i)), '.ext1') then
begin
// handle my file..
// break if needed
end else
if SameText(ExtractFileExt(ParamStr(i)), '.ext2') then
begin
// handle my file..
// break if needed
end else
end;
end;
That works pretty much how I want it to, but when I was testing I realised it does not consider using only one instance of my program.
So for example, if I selected several Files from Windows and opened them all at the same time, this will create the same number of instances of my program with the number of Files being opened.
What would be a good way to approach this, so that instead of several instances of my program being opened, any additional Files from Windows being opened will simply focus back to the one and only instance, and I handle the Files as normal?
Thanks
UPDATE
I found a good article here: http://www.delphidabbler.com/articles?article=13&part=2 which I think is what I need, and shows how to work with the Windows API as mentioned by rhooligan. I am going to read through it now..
Here is some simple example code that gets the job done. I hope it is self-explanatory.
program StartupProject;
uses
SysUtils,
Messages,
Windows,
Forms,
uMainForm in 'uMainForm.pas' {MainForm};
{$R *.res}
procedure Main;
var
i: Integer;
Arg: string;
Window: HWND;
CopyDataStruct: TCopyDataStruct;
begin
Window := FindWindow(SWindowClassName, nil);
if Window=0 then begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end else begin
FillChar(CopyDataStruct, Sizeof(CopyDataStruct), 0);
for i := 1 to ParamCount do begin
Arg := ParamStr(i);
CopyDataStruct.cbData := (Length(Arg)+1)*SizeOf(Char);
CopyDataStruct.lpData := PChar(Arg);
SendMessage(Window, WM_COPYDATA, 0, NativeInt(#CopyDataStruct));
end;
SetForegroundWindow(Window);
end;
end;
begin
Main;
end.
unit uMainForm;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls;
type
TMainForm = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
public
procedure ProcessArgument(const Arg: string);
end;
var
MainForm: TMainForm;
const
SWindowClassName = 'VeryUniqueNameToAvoidUnexpectedCollisions';
implementation
{$R *.dfm}
{ TMainForm }
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WinClassName := SWindowClassName;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 1 to ParamCount do begin
ProcessArgument(ParamStr(i));
end;
end;
procedure TMainForm.ProcessArgument(const Arg: string);
begin
ListBox1.Items.Add(Arg);
end;
procedure TMainForm.WMCopyData(var Message: TWMCopyData);
var
Arg: string;
begin
SetString(Arg, PChar(Message.CopyDataStruct.lpData), (Message.CopyDataStruct.cbData div SizeOf(Char))-1);
ProcessArgument(Arg);
Application.Restore;
Application.BringToFront;
end;
end.
The logic goes something like this. When you start your application, you iterate through the list of running processes and see if your application is already running. If it is running, you need to activate the window of that instance and then exit.
Everything you need to do this is in the Windows API. I found this sample code on CodeProject.com that deals with processes:
http://www.codeproject.com/KB/system/Win32Process.aspx
On finding and activating a window, the basic approach is to find the window of interest using the window class name then activate it.
http://www.vb6.us/tutorials/activate-window-api
Hopefully this gives you a good starting point.
There are many answers here that show how to implement this. I want to show why NOT to use the FindWindow approach.
I am using FindWindow (something similar with the one shown by David H) and I have seen it failed starting with Win10 - I don't know what they changed in Win10.
I think the gap between the time when the app starts and the time when we set the unique ID via CreateParams is too big so another instance has somehow time to run in this gap/interval.
Imagine two instances started at only 1ms distance (let's say that the user click the EXE file and then presses enter and keeps it pressed by accident for a short while). Both instances will check to see if a window with that unique ID exists, but none of them had the chance to set the flag/unique ID because creating the form is slow and the unique ID is set only when the form is constructed. So, both instances will run.
So, I would recommend the CreateSemaphore solution instead:
https://stackoverflow.com/a/460480/46207
Marjan V already proposed this solution but didn't explained why it is better/safer.
I'd use mutexes. You create one when your program starts.
When the creation fails it means another instance is already running. You then send this instance a message with your command line parameters and close. When your app receives a message with a command line, it can parse the parameters like you are already doing, check to see whether it already has the file(s) open and proceed accordingly.
Processing this app specific message ia also the place to get your app to the front if it isn't already. Please do this politely (SetForegroundWindow) without trying to force your app in front of all others.
function CreateMutexes(const MutexName: String): boolean;
// Creates the two mutexes to see if the program is already running.
// One of the mutexes is created in the global name space (which makes it
// possible to access the mutex across user sessions in Windows XP); the other
// is created in the session name space (because versions of Windows NT prior
// to 4.0 TSE don't have a global name space and don't support the 'Global\'
// prefix).
var
SecurityDesc: TSecurityDescriptor;
SecurityAttr: TSecurityAttributes;
begin
// By default on Windows NT, created mutexes are accessible only by the user
// running the process. We need our mutexes to be accessible to all users, so
// that the mutex detection can work across user sessions in Windows XP. To
// do this we use a security descriptor with a null DACL.
InitializeSecurityDescriptor(#SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(#SecurityDesc, True, nil, False);
SecurityAttr.nLength := SizeOf(SecurityAttr);
SecurityAttr.lpSecurityDescriptor := #SecurityDesc;
SecurityAttr.bInheritHandle := False;
if (CreateMutex(#SecurityAttr, False, PChar(MutexName)) <> 0 )
and (CreateMutex(#SecurityAttr, False, PChar('Global\' + MutexName)) <> 0 ) then
Result := True
else
Result := False;
end;
initialization
if not CreateMutexes('MyAppNameIsRunningMutex') then
//Find and SendMessage to running instance
;
end.
Note: above code is adapted from an example on the InnoSetup site. InnoSetup creates installer applications and uses this approach in the installer to check whether (a previous version of) the application being installed is already running.
Finding the other instance and sending it a message, I'll leave for another question (or you can use the WM_COPYDATA approach from David's answer). Actually, there is a StackOverflow question that deals exactly with this: How to get the process thread that owns a mutex Getting the process/thread that owns the mutex may be a bit of a challenge, but the answers to this question do address ways to get the information from one instance to the other.
Windows has different ways to handle file associations to executable.
The "command line" approach is only the simplest one, but also the most limited one.
It also supports DDE (it still works although officially deprecated) and COM (see http://msdn.microsoft.com/en-us/library/windows/desktop/cc144171(v=vs.85).aspx).
If I recall correctly both DDE and COM will let your application receive the whole list of selected files.
I used window/message approach by myself with addition of events for tracking if the other instance is running:
Try to create event "Global\MyAppCode" (the "Global" namespace is used for handling various user sessions as I needed single instance system-wide; in your case you'll probably prefer "Local" namespace which is set by default)
If CreateEvent returned error and GetLastError = ERROR_ALREADY_EXISTS then the instance is running already.
FindWindow/WM_COPYDATA to transfer data to that instance.
But the drawbacks with messages/windows are more than significant:
You must always keep your window's Caption constant. Otherwise you'll have to list all the windows in the system and loop through them for partial occurrence of some constant part. Moreover the window's caption could be easily changed by a user or 3rd part app so the search would fail.
Method requires a window to be created so no console/service apps, or they must create a window and perform message loop especially for handling the single instance.
I'm not sure FindWindow could find a window that is opened in another user session
For me, WM_COPYDATA is rather awkward method.
So currently I'm a fan of named pipe approach (haven't implemented it yet though).
On launch, app tries to connect to "Global\MyAppPipe". If successed, other instance is running. If failed, it creates this pipe and finishes instance check.
2nd instance writes the required data to pipe and exits.
1st instance receives data and does some stuff.
It works through all user sessions (with namespace "Global") or just a current session; it doesn't depend on strings used by UI (no localization and modification issues); it works with console and service apps (you'll need to implement pipe reading in a separate thread/message loop though).

Resources