my first post on stackoverflow.
I'm not much of a coder, I dabble in coding sometimes for fun and as such I don't invest much time in understanding the fundamentals, but rather find whatever solution that works, even if it's a little "ugly".
Which brings me to my problem: I wrote a simple winapi program in C with one dialog box and one DlgProc. It accepts files and does something with them, lets say, for the sake of simplification that all it does is create a copy of the file with the extension *.BAK.
I've added a key to the registry (HKEY_CLASSES_ROOT*\shell\BKUP\command) so that I can select several files in windows explorer and have the option "Create Backup" to send all their names to my program but that calls my program for each file separately. So I googled, did some reading, turns out I need something called IPC (Interprocess Communications), read some options, WM_COPYDATA message looked like it was the simplest and easiest solution, so I used it and it works like a charm, BUT, sometimes it just doesn't... First I'll explain what I do:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
COPYDATASTRUCT dsIPC;
hwnd=FindWindow("#32770","Backup program");
if(hwnd)
{
// send "__argv[1]" via SendMessage(hwnd,WM_COPYDATA... etc.
return(0);
}
return DialogBox(hInstance, MAKEINTRESOURCE(ID_DIALOG), NULL, DlgProc);
}
I use FindWindow() to check if there is an instance of the program running, if not it runs normally, if yes I send the file name to the window found by FindWindow() and exit that instance of the program completely.
Inside the dialog process I have code to fill an array with the names of the files and for each set a short timer with SetTimer() and when all the file names are received the timer goes off and I start copying the files.
Again, all this works great but on occasion, two or even 3 instances of the program are opening and the files are split between them, which means FindWindow() sometimes fails to find the 1st window. example:
I select 10 files in windows explorer, right click them and choose "Create Backup". 2 windows of my program open.
1st window output:
"File 00.DAT" Backed up successfully.
"File 01.DAT" Backed up successfully.
2nd window output:
"File 02.DAT" Backed up successfully.
"File 03.DAT" Backed up successfully.
"File 04.DAT" Backed up successfully.
"File 05.DAT" Backed up successfully.
"File 06.DAT" Backed up successfully.
"File 07.DAT" Backed up successfully.
"File 08.DAT" Backed up successfully.
"File 09.DAT" Backed up successfully.
And then, I close the 2 windows and again select the same 10 files and again choose "Create Backup" but this time and for the next several attempts I get only one window:
1st window output:
"File 00.DAT" Backed up successfully.
"File 01.DAT" Backed up successfully.
"File 02.DAT" Backed up successfully.
"File 03.DAT" Backed up successfully.
"File 04.DAT" Backed up successfully.
"File 05.DAT" Backed up successfully.
"File 06.DAT" Backed up successfully.
"File 07.DAT" Backed up successfully.
"File 08.DAT" Backed up successfully.
"File 09.DAT" Backed up successfully.
Can anyone explain why does it happen?
P.S. If it matters, I'm testing on Windows 7 x64.
[Edit - May 16th 2017] Here is the dumbed down version of zett42's code, it works great for testing but on my to-do-list I wrote down to read and understand the rest of zett42's code because mine's probably flawed in some way.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
COPYDATASTRUCT dsIPC;
int i;
DWORD err;
HANDLE hMutex;
hMutex = CreateMutex(NULL, TRUE, "56f0e348-2c1a-4e01-a98e-3e6c8198f9aa");
err = GetLastError();
if(!hMutex)
{
MessageBox(NULL, "Cannot create mutex object. Click OK to exit.", "Error:", MB_OK | MB_ICONSTOP);
return (1);
}
if(err == ERROR_ALREADY_EXISTS)
{
for(i=0 ; i<1000 ; i++)
{
hwnd=FindWindow("#32770","Backup program");
if(hwnd) break;
Sleep(30);
}
if(i==1000) return (1);
dsIPC.dwData=666;
dsIPC.cbData=lstrlen(__argv[1])+1;
dsIPC.lpData=__argv[1];
SendMessage(hwnd, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)&dsIPC);
return(0);
}
return DialogBox(hInstance, MAKEINTRESOURCE(ID_DIALOG), NULL, DlgProc);
}
As commenter treintje suggested:
There could be a race condition happening where two of your program instances are started at the same time and neither of them had the chance to create a dialog window yet, causing FindWindow to return NULL in both cases. You could prevent this by using a mutex object to check if another instance is already running.
Here is a code sample to show how such mutex could be used to avoid the race condition:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
// Try to create a mutex. Replace the string by something globally unique,
// for instance a GUID created by using the GuidGen utility, that comes with
// Visual Studio (look in the "Extras" menu).
HANDLE hMutex = CreateMutexW(nullptr, TRUE, L"REPLACE-WITH-YOUR-GUID");
// Make sure to put no other code in between the CreateMutex() and the
// GetLastError() calls to prevent the last error value from being messed up.
DWORD err = GetLastError();
if(!hMutex)
{
// TODO: error handling
return 1;
}
if(err == ERROR_ALREADY_EXISTS)
{
// An instance of this process is already running, but it might not
// have created the window yet, so FindWindow() could still fail.
// You could call FindWindow() in a loop but that would waste resources.
// So I'm using an event object to wait until the window has been created.
// This event object must be set to "signaled" state in WM_INITDIALOG
// handler of the dialog.
HANDLE hWindowCreatedEvent = CreateEventW(nullptr, TRUE, FALSE,
L"PUT-ANOTHER-GUID-HERE");
if(hWindowCreatedEvent)
{
// Wait with timeout of 30s because the 1st process might have failed
// to create the window for some reason.
DWORD waitRes = WaitForSingleObject(hWindowCreatedEvent, 30 * 1000);
if(waitRes == WAIT_OBJECT_0)
{
// The event is signaled so now we know for sure that the window
// has been created.
HWND hwnd;
COPYDATASTRUCT dsIPC;
hwnd=FindWindow("#32770","Backup program");
if(hwnd)
{
// send "__argv[1]" via SendMessage(hwnd,WM_COPYDATA... etc.
}
}
else
{
// TODO: error handling
}
CloseHandle(hWindowCreatedEvent);
}
else
{
// TODO: error handling
}
}
else
{
// This is the first instance of this process.
return DialogBox(hInstance, MAKEINTRESOURCE(ID_DIALOG), NULL, DlgProc);
}
CloseHandle(hMutex);
}
Edit your DialogProc to set the event object that signals other instances of the process that the window has been created:
INT_PTR CALLBACK DialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch(uMsg)
{
case WM_INITDIALOG:
{
// Use the same event name as in WinMain()!
HANDLE hWindowCreatedEvent = CreateEventW(nullptr, TRUE, FALSE,
L"PUT-GUID-FROM-WINMAIN-HERE");
if(hWindowCreatedEvent)
{
SetEvent(hWindowCreatedEvent);
CloseHandle(hWindowCreatedEvent);
}
// other initialization code...
return TRUE;
}
}
return FALSE;
}
Another suggestion: Don't search for the window caption because "Backup program" is way to generic and could be used by other applications. Then you would send WM_COPYDATA
to wrong process.
Instead, register a window class with a globally unique name and only search for the class name (call FindWindow()
with NULL as the argument for lpWindowName
). You must also specify the class name in the dialog template.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments