草庐IT

c - FindWindow() 偶尔失败(尝试 IPC)

coder 2024-06-20 原文

我在 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.

谁能解释为什么会这样?

附言如果重要的话,我正在 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);
}

最佳答案

正如评论者 treintje 所建议的:

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.

这是一个代码示例,展示了 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;
}

另一个建议:不要搜索窗口标题,因为“备份程序”是通用的方式,可以被其他应用程序使用。然后,您会将 WM_COPYDATA 发送到错误的进程。

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

关于c - FindWindow() 偶尔失败(尝试 IPC),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43959259/

有关c - FindWindow() 偶尔失败(尝试 IPC)的更多相关文章

  1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  2. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  3. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  4. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

  5. ruby - 正则表达式在哪个位置失败? - 2

    我需要一个非常简单的字符串验证器来显示第一个符号与所需格式不对应的位置。我想使用正则表达式,但在这种情况下,我必须找到与表达式相对应的字符串停止的位置,但我找不到可以做到这一点的方法。(这一定是一种相当简单的方法……也许没有?)例如,如果我有正则表达式:/^Q+E+R+$/带字符串:"QQQQEEE2ER"期望的结果应该是7 最佳答案 一个想法:你可以做的是标记你的模式并用可选的嵌套捕获组编写它:^(Q+(E+(R+($)?)?)?)?然后你只需要计算你获得的捕获组的数量就可以知道正则表达式引擎在模式中停止的位置,你可以确定匹配结束

  6. ruby - 使用 rbenv 和 ruby​​-build 构建 ruby​​ 失败,出现 undefined symbol : SSLv2_method - 2

    我正在尝试在配备ARMv7处理器的SynologyDS215j上安装ruby​​2.2.4或2.3.0。我用了optware-ng安装gcc、make、openssl、openssl-dev和zlib。我根据README中的说明安装了rbenv(版本1.0.0-19-g29b4da7)和ruby​​-build插件。.这些是随optware-ng安装的软件包及其版本binutils-2.25.1-1gcc-5.3.0-6gconv-modules-2.21-3glibc-opt-2.21-4libc-dev-2.21-1libgmp-6.0.0a-1libmpc-1.0.2-1libm

  7. ruby-on-rails - Ruby 的 'open_uri' 是否在读取或失败后可靠地关闭套接字? - 2

    一段时间以来,我一直在使用open_uri下拉ftp路径作为数据源,但突然发现我几乎连续不断地收到“530抱歉,允许的最大客户端数(95)已经连接。”我不确定我的代码是否有问题,或者是否是其他人在访问服务器,不幸的是,我无法真正确定谁有问题。本质上,我正在读取FTPURI:defself.read_uri(uri)beginuri=open(uri).readuri=="Error"?nil:urirescueOpenURI::HTTPErrornilendend我猜我需要在这里添加一些额外的错误处理代码...我想确保我采取一切预防措施来关闭所有连接,这样我的连接就不是问题所在,但是我

  8. ruby-on-rails - Ruby 流量控制 : throw an exception, 返回 nil 还是让它失败? - 2

    我在思考流量控制的最佳实践。我应该走哪条路?1)不要检查任何东西并让程序失败(更清晰的代码,自然的错误消息):defself.fetch(feed_id)feed=Feed.find(feed_id)feed.fetchend2)通过返回nil静默失败(但是,“CleanCode”说,你永远不应该返回null):defself.fetch(feed_id)returnunlessfeed_idfeed=Feed.find(feed_id)returnunlessfeedfeed.fetchend3)抛出异常(因为不按id查找feed是异常的):defself.fetch(feed_id

  9. ruby - gem 规范失败 - 2

    我正在为毕业设计开发GEM,TravisCI构建不断失败。这是我在Travis上的链接:https://travis-ci.org/ricardobond/perpetuus/builds/8709218构建错误是:$bundleexecrakerakeaborted!Don'tknowhowtobuildtask'default'/home/travis/.rvm/gems/ruby-1.9.3-p448/bin/ruby_noexec_wrapper:14:in`eval'/home/travis/.rvm/gems/ruby-1.9.3-p448/bin/ruby_noexec_

  10. ruby-on-rails - 尝试设置 Amazon 的 S3 存储桶 : 403 Forbidden error & setting permissions - 2

    我正在关注Hartl的railstutorial.org并已到达11.4.4:Imageuploadinproduction.我做了什么:注册亚马逊网络服务在AmazonIdentityandAccessManagement中,我创建了一个用户。用户创建成功。在AmazonS3中,我创建了一个新存储桶。设置新存储桶的权限:权限:本教程指示“授予上一步创建的用户读写权限”。但是,在存储桶的“权限”下,未提及新用户名。我只能在每个人、经过身份验证的用户、日志传送、我和亚马逊似乎根据我的名字+数字创建的用户名之间进行选择。我已经通过选择经过身份验证的用户并选中了上传/删除和查看权限的框(而不

随机推荐