使用处理程序读取c#中的c++窗口

本文关键字:中的 c++ 窗口 读取 处理 程序 | 更新日期: 2023-09-27 18:26:55

我正在C#中为一个名为AmiBroker的产品实现一个插件。

AmiBroker是一款交易软件,它公开了一些功能,第三方供应商可以使用这些功能将股票数据传递给解决方案。因此,我们可以在C#中创建一个可以被AmiBroker识别的插件。

在我的场景中,我得到了AmiBroker主窗口的一个处理程序[注意:AmiBroker完全用C++编写]在C#中,我们可以检索主窗口的处理程序,所以使用这个处理程序我可以读取窗口的数据,例如子窗口、显示库存列表的面板或用户可见的东西,如果是这样,我该怎么做?

使用处理程序读取c#中的c++窗口

你可以,但这很混乱。我只是做了一些非常相似的事情。Pinvoke.net非常适合这些东西,但我将向您展示一些如何找到控件的例子。如果AmiBroker有任何关于控件名称或AccessibleNames的文档,或者任何可以让你找到你想要的确切控件的文档,那将是致命的。因为如果它们的名字不明确,你会很难找到你特别想要的。但基本上,您要做的是在您拥有的句柄上执行EnumChildWindows,对它们进行迭代,并寻找一个唯一的属性,以便找到您想要的控件。然后,您需要执行一个特定的SendMessage来获取控件中的文本(GetWindowText或它所称的任何东西只适用于标签)。代码如下,在某个时刻从Pinvoke.net改编或刷出(伟大的起点):

    [DllImport("user32")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
    public static extern uint GetClassName(IntPtr handle, StringBuilder name, int maxLength);
    public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
    private static List<IntPtr> GetChildWindows(IntPtr parent)
    {
        List<IntPtr> result = new List<IntPtr>();
        GCHandle listHandle = GCHandle.Alloc(result);
        try
        {
            EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
            EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
        }
        finally
        {
            if (listHandle.IsAllocated)
                listHandle.Free();
        }
        return result;
    }
    private static bool EnumWindow(IntPtr handle, IntPtr pointer)
    {
        GCHandle gch = GCHandle.FromIntPtr(pointer);
        List<IntPtr> list = gch.Target as List<IntPtr>;
        if (list == null)
        {
            throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
        }
        list.Add(handle);
        //  You can modify this to check to see if you want to cancel the operation, then return a null here
        return true;
    }
    //THIS IS THE ONE YOU'LL CALL!
    public static IntPtr GetWindowByClass(IntPtr mainWindow, string name)
    {
        List<IntPtr> windows = GetChildWindows(mainWindow);
        foreach (IntPtr window in windows)
        {
            StringBuilder response = new StringBuilder();
            response.Capacity = 500;
            if (GetClassName(window, response, response.Capacity) > 0)
                if (response.ToString() == name)
                    return window;
        }
        return IntPtr.Zero;
    }

因此,基本上,它会在一整套子窗口中迭代你在应用程序上的句柄,查看类名是否与你正在寻找的控件匹配,然后返回它。有成千上万种方法可以改进它(一次搜索所有你想要的控件,FindWindow可能按类名工作,等等),但我想向你展示更多它是如何做到的,不要声明应该这样做。最后,从窗口/控件获取文本的调用如下(也改编自pinvoke.net:在User32.dll下查找所有这些内容):

    public static string GetText(IntPtr control)
    {
        StringBuilder builder = new StringBuilder(40);
        IntPtr result = IntPtr.Zero;
        uint response = SendMessageTimeoutText(control, 0xd, 40, builder, APITypes.SendMessageTimeoutFlags.SMTO_NORMAL, 2000, out result);
        return builder.ToString();
    }
    [DllImport("user32.dll", EntryPoint = "SendMessageTimeout", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern uint SendMessageTimeoutText(
        IntPtr hWnd,
        int Msg,              // Use WM_GETTEXT
        int countOfChars,
        StringBuilder text,
        APITypes.SendMessageTimeoutFlags flags,
        uint uTImeoutj,
        out IntPtr result);
    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL = 0x0,
        SMTO_BLOCK = 0x1,
        SMTO_ABORTIFHUNG = 0x2,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x8
    }

编辑:一个附录:我为访问另一个这样的表单而开发的应用程序实际上没有唯一的控件名称,所以我最终使用Spy++来确定它在窗口层次结构中的位置,并拉动子级,依次选择每个子级。如果你必须走这条路,上帝保佑你,尤其是因为它可能根本不一致,尤其是如果你需要的是一个没有创建的表单,或者它隐藏在另一个Z顺序的表单后面(打破你正在搜索的层次列表)。也就是说,您应该知道EnumChildWindows将始终为给定窗口枚举ALL CHILD WINDOWS,无论它们在层次结构中的什么位置。如果您真的必须按每个控件的父控件和父控件的父级向下搜索,则需要使用FindWindowEx,并声明您查看的最后一个子控件(如果您想要第一个子控件,则声明IntPtr.Zero):

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

如果编写插件的软件提供API,那么您所要求的内容似乎与直觉相悖。你真的应该使用它。

虽然可以使用Win32 API在给定句柄的情况下枚举主窗口的子窗口,然后使用更多的Win32 API函数来确定UI的状态(即"读取数据"),但这将是非常冗长和错误的。

这里有一个指向MSDN for EnumChildWindows的链接,它将允许您为给定句柄的主窗口枚举子窗口。

如果你想深入兔子洞,你可能会发现SendMessage和GetWindowText很有用。如果您要使用C#中的这些Win32 API,那么您肯定应该查看pinvoke.net。