【问题标题】:Why can't get the main window handle for a started process?为什么无法获取已启动进程的主窗口句柄?
【发布时间】:2011-06-11 06:03:45
【问题描述】:

我有一种情况,我在我的代码中启动一个进程以设置 IPC 通道。我开始的进程是一个不支持 CLR 的 MFC 应用程序。我开始此过程的应用程序是 WPF 应用程序中的 C# 模块(我认为这与我的问题无关)。这适用于支持 CLR 的应用程序版本,并且它适用于除部署目标(装有 Windows 7 的触摸屏计算机)之外的每台计算机。但由于某种原因,当我在这个确切的场景中尝试它时,进程对象永远不会解析主窗口句柄 (Process.MainWindowHandle)。是否有另一种(甚至可能是 pinvoke)方法来做到这一点?这是安全问题吗?我是那个盯着这个过程的人。进程的主窗口句柄确实存在。我看不出有什么问题。

如果有帮助,这是我的代码。

        _applicationProcess = new Process();
        _applicationProcess.StartInfo.FileName = _strProcessPath;
        _applicationProcess.StartInfo.Arguments = _strProcessArguments;
        _applicationProcess.Start();

        long nTicks = Environment.TickCount;
        if (_applicationProcess.WaitForInputIdle(1 /*minute(s)*/ * 60000))
        {
            try
            {
                do
                {
                    // Don't let total processing take more than 1 minute(s).
                    if (Environment.TickCount > nTicks + 1 /*minute(s)*/ * 60000)
                        throw new ApplicationException("MFCApplication.Startup failed! The main window handle is zero!");

                    _applicationProcess.Refresh();
                }
                while (_applicationProcess.MainWindowHandle.ToInt32() == 0);

                _applicationHandle = new IntPtr(_applicationProcess.MainWindowHandle.ToInt32());
            }
            catch (Exception ex)
            {
                //Do some stuff...
                throw;
            }
        }
        else
        {
            // Do exception handling.
        }

在尝试获取非零的主窗口句柄一分钟后,ApplicationException 被击中。

【问题讨论】:

  • 您是否厌倦了按照 MSDN 文档的建议使用 WaitForInputIdle?此外,最好使用throw 而不是throw ex,因为throw ex 会丢失堆栈跟踪信息(throw ex 重新启动异常抛出并因此重新启动堆栈跟踪)。
  • @jordan 我看到了你的编辑。您需要在您的 cmets 中包含 @username,以便通知您正在回复的其他用户。
  • @jordan 在您的 do\while 循环中,我认为您将滴答声与毫秒混淆了。一个滴答声是 100 纳秒。为此使用Stopwatch 可能更直接。
  • @jordan 可能值得考虑在你的循环中加入一个Thread.Sleep 来限制你的Refresh 调用。
  • @chibacity 不,这些都不起作用。而且我认为 @ 没有必要,因为你是唯一的评论者。

标签: c# wpf mfc process ipc


【解决方案1】:

不幸的是,您从 Process.MainWindowHandle 中得到的值是一个猜测。程序没有可用的 API 函数来告诉 Windows“这是我的主窗口”。它使用的规则已记录在案,它是进程启动时创建的第一个窗口。如果第一个窗口是登录窗口或启动屏幕,则会导致麻烦。

您对此无能为力,您必须更多地了解程序的行为方式才能找到真正的主窗口。使用 EnumThreadWindows() 枚举窗口可以帮助您找到它,只要第一个窗口是在与主窗口相同的线程上创建的。如果不是这种情况,则需要更详细的 EnumWindows()。

【讨论】:

    【解决方案2】:

    我的习惯是循环调用 EnumWindows 结合 GetWindowThreadProcessId 来查找窗口句柄。

    C Code, adapt to your language
    
    
    DWORD TargetHWND;
    
    //...
        while (EnumWindows(EnumWndProc, (LPARAM)(DWORD)pid)) {
            Sleep(100);
        }
    
    
    //...
    
    BOOL EnumWndProc(HWND hWnd, LPARAM lParam) {
        DWORD pid = (DWORD)-1;
        GetWindowThreadProcessId(hWnd, &pid);
        if (pid == (DWORD)lParam) {
            TargetHWND = hWnd;
            return FALSE;
        }
        return TRUE;
    }
    

    【讨论】:

    • 这对我有什么帮助?我正在尝试从另一个进程获取窗口句柄。能给我一些代码吗?
    • 所以基本上这会获取给定进程 ID 的所有窗口,很有趣。
    【解决方案3】:

    为了通过您的进程获得MainWindowHandle,请确保您的WPF应用程序显示在任务栏上,即ShowInTaskbar="True"并将Application.Current.MainWindow属性设置为您想设置为主窗口窗口。

    如果我在我的 WPF 主窗口中执行下面的代码而不设置 ShowInTaskbar="True" 我总是得到 0 作为 MainWindowHandle 因为我的 WPF 窗口是全屏的并且没有显示在任务栏上。

        Application.Current.MainWindow = this;
        var Query = System.Diagnostics.Process.GetProcessesByName("ProcessName");
    
        if (Query.Any())
        {
            Query.FirstOrDefault().Refresh();
            MessageBox.Show(Query.FirstOrDefault().MainWindowHandle.ToInt32().ToString());
        }
    

    【讨论】:

      【解决方案4】:

      我不知道为什么会有所不同,但是在您创建流程之后,尝试这样做:

      Process[] allProcesses = Process.GetProcessesByName("YourWindowTitle");
      

      并查看返回的任何进程是否有 MainWindowHandle。

      【讨论】:

      • 这是不同的,但方式相反。以这种方式掌握一个流程,您没有完全信任,因此无法对它做很多事情,当然也无法掌握它。只有当您启动一个流程时,您才能与它进行任何有意义的交互。
      猜你喜欢
      • 2010-11-28
      • 2010-12-25
      • 1970-01-01
      • 2011-04-12
      • 2011-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多