从 C# 注册自定义的 win32 窗口类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/128561/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 14:48:29  来源:igfitidea点击:

Registering a custom win32 window class from c#

提问by morechilli

I have a new application written in WPF that needs to support an old API that allows it to receive a message that has been posted to a hidden window. Typically another application uses FindWindow to identify the hidden window using the name of its custom window class.

我有一个用 WPF 编写的新应用程序,它需要支持旧 API,允许它接收已发布到隐藏窗口的消息。通常,另一个应用程序使用 FindWindow 使用其自定义窗口类的名称来识别隐藏窗口。

1) I assume to implement a custom window class I need to use old school win32 calls?

1)我假设要实现一个自定义窗口类,我需要使用老式的 win32 调用?

My old c++ application used RegisterClass and CreateWindow to make the simplest possible invisible window.

我的旧 C++ 应用程序使用 RegisterClass 和 CreateWindow 来制作最简单的隐形窗口。

I believe I should be able to do the same all within c#. I don't want my project to have to compile any unmanaged code.

我相信我应该能够在 c# 中做同样的事情。我不希望我的项目必须编译任何非托管代码。

I have tried inheriting from System.Windows.Interop.HwndHost and using System.Runtime.InteropServices.DllImport to pull in the above API methods.

我尝试从 System.Windows.Interop.HwndHost 继承并使用 System.Runtime.InteropServices.DllImport 来引入上述 API 方法。

Doing this I can successfully host a standard win32 window e.g. "listbox" inside WPF. However when I call CreateWindowEx for my custom window it always returns null.

这样做我可以成功地在 WPF 中托管一个标准的 win32 窗口,例如“列表框”。但是,当我为自定义窗口调用 CreateWindowEx 时,它始终返回 null。

My call to RegisterClass succeeds but I am not sure what I should be setting the WNDCLASS.lpfnWndProc member to.

我对 RegisterClass 的调用成功了,但我不确定我应该将 WNDCLASS.lpfnWndProc 成员设置为什么。

2) Does anyone know how to do this successfully?

2)有谁知道如何成功做到这一点?

采纳答案by morechilli

For the record I finally got this to work. Turned out the difficulties I had were down to string marshalling problems. I had to be more precise in my importing of win32 functions.

为了记录,我终于让这个工作了。原来我遇到的困难归结为字符串编组问题。我必须更精确地导入 win32 函数。

Below is the code that will create a custom window class in c# - useful for supporting old APIs you might have that rely on custom window classes.

下面是将在 c# 中创建自定义窗口类的代码 - 用于支持您可能拥有的依赖于自定义窗口类的旧 API。

It should work in either WPF or Winforms as long as a message pump is running on the thread.

只要消息泵在线程上运行,它就可以在 WPF 或 Winforms 中工作。

EDIT: Updated to fix the reported crash due to early collection of the delegate that wraps the callback. The delegate is now held as a member and the delegate explicitly marshaled as a function pointer. This fixes the issue and makes it easier to understand the behaviour.

编辑:更新以修复由于包装回调的委托的早期收集而报告的崩溃。委托现在作为成员持有,委托显式编组为函数指针。这解决了这个问题,并使人们更容易理解行为。

class CustomWindow : IDisposable
{
    delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    [System.Runtime.InteropServices.StructLayout(
        System.Runtime.InteropServices.LayoutKind.Sequential,
       CharSet = System.Runtime.InteropServices.CharSet.Unicode
    )]
    struct WNDCLASS
    {
        public uint style;
        public IntPtr lpfnWndProc;
        public int cbClsExtra;
        public int cbWndExtra;
        public IntPtr hInstance;
        public IntPtr hIcon;
        public IntPtr hCursor;
        public IntPtr hbrBackground;
        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
        public string lpszMenuName;
        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
        public string lpszClassName;
    }

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern System.UInt16 RegisterClassW(
        [System.Runtime.InteropServices.In] ref WNDCLASS lpWndClass
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr CreateWindowExW(
       UInt32 dwExStyle,
       [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
       string lpClassName,
       [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
       string lpWindowName,
       UInt32 dwStyle,
       Int32 x,
       Int32 y,
       Int32 nWidth,
       Int32 nHeight,
       IntPtr hWndParent,
       IntPtr hMenu,
       IntPtr hInstance,
       IntPtr lpParam
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern System.IntPtr DefWindowProcW(
        IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern bool DestroyWindow(
        IntPtr hWnd
    );

    private const int ERROR_CLASS_ALREADY_EXISTS = 1410;

    private bool m_disposed;
    private IntPtr m_hwnd;

    public void Dispose() 
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) 
    {
        if (!m_disposed) {
            if (disposing) {
                // Dispose managed resources
            }

            // Dispose unmanaged resources
            if (m_hwnd != IntPtr.Zero) {
                DestroyWindow(m_hwnd);
                m_hwnd = IntPtr.Zero;
            }

        }
    }

    public CustomWindow(string class_name){

        if (class_name == null) throw new System.Exception("class_name is null");
        if (class_name == String.Empty) throw new System.Exception("class_name is empty");

        m_wnd_proc_delegate = CustomWndProc;

        // Create WNDCLASS
        WNDCLASS wind_class = new WNDCLASS();
        wind_class.lpszClassName = class_name;
        wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);

        UInt16 class_atom = RegisterClassW(ref wind_class);

        int last_error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

        if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS) {
            throw new System.Exception("Could not register window class");
        }

        // Create window
        m_hwnd = CreateWindowExW(
            0,
            class_name,
            String.Empty,
            0,
            0,
            0,
            0,
            0,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero
        );
    }

    private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) 
    {
        return DefWindowProcW(hWnd, msg, wParam, lParam);
    }

    private WndProc m_wnd_proc_delegate;
}

回答by chakrit

1)You can just subclass a normal Windows Forms class... no need for all those win32 calls, you just need to parse the WndProc message manually... is all.

1)您可以子类化一个普通的 Windows 窗体类......不需要所有这些 win32 调用,您只需要手动解析 WndProc 消息......就是全部。

2)You can import the System.Windows.Forms namespace and use it alongside WPF, I believe there won't be any problems as long as you don't intertwine too much windows forms into your WPF application. You just want to instantiate your custom hidden form to receieve a message is that right?

2)您可以导入 System.Windows.Forms 命名空间并将其与 WPF 一起使用,我相信只要您不将过多的 Windows 窗体交织到您的 WPF 应用程序中就不会出现任何问题。您只是想实例化您的自定义隐藏表单以接收消息,对吗?

example of WndProc subclassing:

WndProc 子类化的示例:

protected override void WndProc(ref System.Windows.Forms.Message m)
{
   // *always* let the base class process the message
   base.WndProc(ref m);

   const int WM_NCHITTEST = 0x84;
   const int HTCAPTION = 2;
   const int HTCLIENT = 1;

   // if Windows is querying where the mouse is and the base form class said
   // it's on the client area, let's cheat and say it's on the title bar instead
   if ( m.Msg == WM_NCHITTEST && m.Result.ToInt32() == HTCLIENT )
      m.Result = new IntPtr(HTCAPTION);
}

Since you already know RegisterClass and all those Win32 calls, I assume the WndProc message wouldn't be a problem for you...

由于您已经知道 RegisterClass 和所有那些 Win32 调用,我认为 WndProc 消息对您来说不是问题......

回答by Paul

WNDCLASS wind_class; put the definition in the class, not the function, and the crash will be fixed.

WNDCLASS wind_class; 将定义放在类中,而不是函数中,崩溃将得到修复。

回答by Martini Bianco

I'd like to comment the answer of morechilli:

我想评论morechilli的答案:

public CustomWindow(string class_name){

    if (class_name == null) throw new System.Exception("class_name is null");
    if (class_name == String.Empty) throw new System.Exception("class_name is empty");

    // Create WNDCLASS
    WNDCLASS wind_class = new WNDCLASS();
    wind_class.lpszClassName = class_name;
    wind_class.lpfnWndProc = CustomWndProc;

    UInt16 class_atom = RegisterClassW(ref wind_class);

    int last_error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

    if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS) {
        throw new System.Exception("Could not register window class");
    }

    // Create window
    m_hwnd = CreateWindowExW(
        0,
        class_name,
        String.Empty,
        0,
        0,
        0,
        0,
        0,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero
    );
}

In the constructor I copied above is slight error: The WNDCLASS instance is created, but not saved. It will eventually be garbage collected. But the WNDCLASS holds the WndProc delegate. This results in an error as soon as WNDCLASS is garbage collected. The instance of WNDCLASS should be hold in a member variable until the window is destroyed.

在我上面复制的构造函数中有轻微错误:WNDCLASS 实例已创建,但未保存。它最终将被垃圾收集。但是 WNDCLASS 持有 WndProc 委托。一旦 WNDCLASS 被垃圾收集,这就会导致错误。WNDCLASS 的实例应该保存在一个成员变量中,直到窗口被销毁。