【问题标题】:FindWindowEx from user32.dll is returning a handle of Zero and error code of 127 using dllimport来自 user32.dll 的 FindWindowEx 使用 dllimport 返回零句柄和错误代码 127
【发布时间】:2011-03-09 06:02:17
【问题描述】:

我需要以编程方式处理另一个 Windows 应用程序,搜索 google 我发现了一个使用 DLLImport 属性处理 Windows 计算器的示例,并将 user32.dll 函数导入到 C# 中的托管函数中。

应用程序正在运行,我正在获取主窗口的句柄,即计算器本身,但之后的代码无法正常工作。 FindWindowEx 方法不返回 Calculator 子项的句柄,如按钮和文本框。

我尝试在 DLLImport 上使用 SetLastError=True,发现我收到错误代码 127,即“找不到程序”。

这是我获得示例应用程序的链接:

http://www.codeproject.com/script/Articles/ArticleVersion.aspx?aid=14519&av=34503

如果有人知道如何解决,请帮忙。

更新:DLLImport 是:

[DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className,  string  windowTitle);

不起作用的代码是:

hwnd=FindWindow(null,"Calculator"); // This is working, I am getting handle of Calculator

// The following is not working, I am getting hwndChild=0 and err = 127
hwndChild = FindWindowEx((IntPtr)hwnd,IntPtr.Zero,"Button","1");

                Int32 err = Marshal.GetLastWin32Error();

【问题讨论】:

  • 您运行的是什么版本的 Windows?
  • 如果您在使用 P/Invoke 时遇到问题,请使用 DllImport 属性发布您的声明。如果您对某些代码有问题,请发布不起作用的代码。
  • @CodyGray 我正在使用 Windows 7 Professional。
  • @Gabe 我已经更新了我的问题并包含了不起作用的代码。

标签: c# winapi pinvoke dllimport findwindow


【解决方案1】:

您尝试的代码依赖于各个按钮的标题来识别它们。例如,它使用以下代码来获取“1”按钮的句柄:

hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "Button", "1");

其中指定“Button”作为窗口类的名称,“1”作为窗口的名称(如果是按钮,这与按钮本身显示的标题文本相同)。

此代码在 Windows XP(和以前的版本)下运行良好,其中计算器按钮由文本标题标识。 “1”按钮的窗口名称为“1”,因此“1”显示为按钮的标题。

但是,在 Windows 7 下,情况似乎发生了变化(可能在 Vista 下也是如此,尽管我无法验证这一点,因为我无法访问这样的系统)。使用 Spy++ 调查计算器窗口确认“1”按钮不再具有窗口名称“1”。事实上,它根本没有窗口名称。标题为 NULL。据推测,计算器的新花哨外观要求自定义绘制按钮,因此不再需要标题来指示哪个按钮对应于哪个功能。自定义绘画例程负责绘制必要的标题。

由于在您指定的窗口文本中找不到任何按钮,因此为窗口句柄返回值 0 (NULL)。

documentation for the FindWindowEx function 表示您可以为lpszWindow 参数指定NULL,但这当然会匹配指定类的所有 窗口。在这种情况下可能不是您想要的,因为计算器应用程序有一堆按钮。

我不知道一个好的解决方法。 Calculator 的设计初衷不是以这种方式“自动化”,而且微软从未保证他们不会改变其内部运作。这是您使用这种方法弄乱其他应用程序的窗口时所冒的风险。


编辑:您链接到的代码在另一个相当严重的方面也是错误的,即使在早期版本的 Windows 上也是如此。它将hwnd 变量声明为int 类型,而不是IntPtr 类型。由于窗口句柄是一个指针,因此您应该始终将其存储为IntPtr 类型。这也修复了 FindWindowEx 函数调用中本应发出危险信号的丑陋演员表。

您还需要修复SendMessage 的声明,使其第一个参数的类型为IntPtr

代码应该是这样写的:

IntPtr hwnd = IntPtr.Zero;
IntPtr hwndChild = IntPtr.Zero;

//Get a handle for the Calculator Application main window
hwnd = FindWindow(null, "Calculator");
if(hwnd == IntPtr.Zero)
{
    if(MessageBox.Show("Couldn't find the calculator" + 
                       " application. Do you want to start it?", 
                       "TestWinAPI", 
                       MessageBoxButtons.YesNo) == DialogResult.Yes)
    {
        System.Diagnostics.Process.Start("Calc");
    }
}
else
{
    //Get a handle for the "1" button
    hwndChild = FindWindowEx(hwnd, IntPtr.Zero, "Button", "1");

    //send BN_CLICKED message
    SendMessage(hwndChild, BN_CLICKED, 0, IntPtr.Zero);

    //Get a handle for the "+" button
    hwndChild = FindWindowEx(hwnd, IntPtr.Zero, "Button", "+");

    //send BN_CLICKED message
    SendMessage(hwndChild, BN_CLICKED, 0, IntPtr.Zero);

    //Get a handle for the "2" button
    hwndChild = FindWindowEx(hwnd, IntPtr.Zero, "Button", "2");

    //send BN_CLICKED message
    SendMessage(hwndChild, BN_CLICKED, 0, IntPtr.Zero);

    //Get a handle for the "=" button
    hwndChild = FindWindowEx(hwnd, IntPtr.Zero, "Button", "=");

    //send BN_CLICKED message
    SendMessage(hwndChild, BN_CLICKED, 0, IntPtr.Zero);
}

【讨论】:

  • 您知道使用 EnumChildWindows 枚举子窗口并检查回调(如果这是我们需要捕获的窗口)的任何方法吗?我是第一次使用 WINAPI,不知道如何编写使用 EnumChildWindows 的代码。
  • @Puneet:要做到这一点,您必须想出某种方法来唯一标识计算器上的按钮。正如我在回答中解释的那样,我不知道这样做的好方法。所有按钮都具有""NULL 的窗口标题(标题)。以编程方式,很难找到一种方法来识别单个按钮,仅给定它们的句柄。
  • 在我的 Win7 机器上,计算器的按钮都具有根据 Spy++ 的标准 Unicode 标题(我在“视图”菜单中尝试了所有 4 种模式)。此外,为了兼容 32 位,所有窗口句柄都可以适应 32 位,因此 HWND 应该可以转换为 int 并返回就好了。
  • @Gabe:很有趣。我在 Server 2008 R2 64 位上检查了我的,它们肯定没有标准的 Unicode 字幕。我刚刚检查了标准视图,但其他视图似乎也没有标题。是的,即使使用int 通常对句柄有效,但这不是正确的做事方式。您应该使用IntPtr,而不是利用向后兼容性。 :-)
  • 我发现了问题所在。我关闭了主题,这导致 Calc 使用 Unicode 按钮标题。您必须启用主题(这不是服务器的默认设置),这会导致 Calc 使用带有空白标题的主题按钮。
【解决方案2】:

我能够在 Win7 Pro 上重现这个。您的问题可能是按钮上的标签是通过计算器的主题绘制的,而不是作为标题。当 Themes 服务运行时,启动 Calculator 会导致它有没有标题的按钮。

为了获得正确的按钮标题,您必须:

  1. 停止主题服务(从提升的命令提示符运行 net stop themes 或使用服务管理工具)。
  2. 启动计算器。

如果您在停止主题服务时运行计算器,您会注意到其所有按钮都变为空白。

【讨论】:

  • 啊,原来你使用的是经典模式。我当然同意这个选择。禁用主题确实似乎是一种解决方法,但不是一个很好的解决方法。您只是在解决代码中的假设,而不是修复这些假设。很难想象只有在非默认配置的 Windows 上才能正常工作的应用程序的实用性。
  • @Cody:看到从 C# 自动化计算器毫无意义,我不得不假设这只是一个练习。很难想象有人实际上正在创建一个依赖于自动计算的应用程序!
  • @Cody:最终我的需要不是通过 C# 自动化计算器,我在我的问题中写道,我找到了一个示例应用程序来了解如何进行实际工作。最后,我需要为另一个 Windows 应用程序实现它,它是一个门票预订系统和报告工具。
  • 即使在我的 Win7 上停止主题后,我也无法通过此应用程序自动化计算器。我仍然将子按钮的句柄设为零。
【解决方案3】:

以下代码在 Windows 7 的经典主题 Caculator 中运行良好(不适用于 Basic 或 Aero 主题):

IntPtr hwndFrame = FindWindowEx(hwnd, IntPtr.Zero, "CalcFrame", null); 
IntPtr hwndDialog = FindWindowEx(hwndFrame, IntPtr.Zero, "#32770", null); 
IntPtr hwndDialog2 = FindWindowEx(hwndFrame, (IntPtr)hwndDialog, "#32770", null);

IntPtr hwndThree = FindWindowEx(hwndDialog2, IntPtr.Zero, "Button", "3"); 
SendMessage((int)hwndThree, BN_CLICKED, 0, IntPtr.Zero);

IntPtr hwndPlus = FindWindowEx(hwndDialog2, IntPtr.Zero, "Button", "+");
SendMessage((int)hwndPlus, BN_CLICKED, 0, IntPtr.Zero);

IntPtr hwndOne = FindWindowEx((IntPtr)hwndDialog2, IntPtr.Zero, "Button", "1");
SendMessage((int)hwndOne, BN_CLICKED, 0, IntPtr.Zero);

IntPtr hwndEqual = FindWindowEx(hwndDialog2, IntPtr.Zero, "Button", "=");
SendMessage((int)hwndEqual, BN_CLICKED, 0, IntPtr.Zero);

【讨论】:

    猜你喜欢
    • 2012-03-23
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    • 2011-08-08
    • 1970-01-01
    • 2018-10-09
    • 2018-04-03
    • 1970-01-01
    相关资源
    最近更新 更多