FindWindow()偶尔会失败(尝试IPC)

时间:2017-05-14 00:29:56

标签: c windows winapi

我在stackoverflow上的第一篇文章。

我不是一个程序员,我有时候喜欢编码以获得乐趣,因此我不会花太多时间来理解基础知识,而是找到有效的解决方案,即使它有点“丑陋”

这让我想到了我的问题:我在C中编写了一个简单的winapi程序,其中包含一个对话框和一个DlgProc。 它接受文件并对它们做一些事情,比方说,为简化起见,它所做的就是创建一个扩展名为* .BAK的文件的副本。

我已经在注册表中添加了一个键(HKEY_CLASSES_ROOT * \ shell \ BKUP \ command),这样我就可以在Windows资源管理器中选择多个文件,并选择“创建备份”将所有名称发送到我的程序,但是分别为每个文件调用我的程序。所以我用谷歌搜索,做了一些阅读,结果我需要一些叫IPC(进程间通信),读取一些选项,WM_COPYDATA消息看起来像是最简单和最简单的解决方案,所以我使用它,它的工作就像一个魅力,但有时它只是没有......首先我会解释我的所作所为:

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);
}

我使用FindWindow()检查程序是否正在运行,如果没有正常运行,如果是,我将文件名发送到FindWindow()找到的窗口,并完全退出该程序实例。

在对话框过程中,我有代码用文件名填充数组,每组用SetTimer()设置一个短计时器,当收到所有文件名时,计时器响起,我开始复制文件。

同样,所有这些都很有效,但偶尔会打开两个甚至3个程序实例,文件在它们之间分开,这意味着FindWindow()有时无法找到第一个窗口。 例如:

我在Windows资源管理器中选择了10个文件,右键单击它们并选择“创建备份”。 打开我程序的2个窗口。

第一个窗口输出:

"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.

然后,我关闭了2个窗口并再次选择相同的10个文件并再次选择“创建备份”,但这次和接下来的几次尝试我只得到一个窗口:

第一个窗口输出:

"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.

任何人都可以解释为什么会这样吗?

P.S。如果重要,我正在Windows 7 x64上进行测试。

[编辑 - 2017年5月16日] 这是zett42代码的愚蠢版本,它非常适合测试,但在我的待办事项列表中,我写下来阅读并理解zett42代码的其余部分,因为我的代码可能存在缺陷。

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);
}

1 个答案:

答案 0 :(得分:2)

评论者treintje建议:

  

在您的两个程序中可能会发生竞争条件   实例同时启动,他们都没有   有机会创建一个对话窗口,导致FindWindow返回   两种情况都为NULL。您可以通过使用互斥对象来防止这种情况   检查另一个实例是否已在运行。

以下是一个代码示例,说明如何使用mutex来避免竞争条件:

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);
}

编辑DialogProc以设置事件对象,该事件对象表示已创建窗口的进程的其他实例:

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;
}

另一个建议:不要搜索窗口标题,因为&#34;备份程序&#34;是通用的方式,可以被其他应用程序使用。然后你会发送WM_COPYDATA错误的过程。

相反,register a window class具有全局唯一名称,仅搜索类名(调用FindWindow(),其中NULL为lpWindowName的参数)。您还必须在对话框模板中指定类名。