Регистрация пользовательских оконных классов win32 из C#

У меня есть новое приложение, написанное на WPF, которое должно поддерживать старый API, который позволяет ему получать сообщение, которое было отправлено в скрытое окно. Обычно другое приложение использует FindWindow для идентификации скрытого окна, используя имя своего пользовательского класса окна.

1) Я предполагаю, что для реализации пользовательского оконного класса мне нужно использовать вызовы win32 старой школы?

Мое старое приложение на C++ использовало RegisterClass и CreateWindow, чтобы сделать самое простое невидимое окно.

Я считаю, что я должен быть в состоянии сделать то же самое в C#. Я не хочу, чтобы мой проект компилировал какой-либо неуправляемый код.

Я попытался унаследовать от System.Windows.Interop.HwndHost и с помощью System.Runtime.InteropServices.DllImport, чтобы использовать вышеупомянутые методы API.

Делая это, я могу успешно разместить стандартное окно win32, например, "listbox" внутри WPF. Однако когда я вызываю CreateWindowEx для моего собственного окна, оно всегда возвращает ноль.

Мой звонок в RegisterClass успешен, но я не уверен, что я должен установить для члена WNDCLASS.lpfnWndProc.

2) Кто-нибудь знает, как это сделать успешно?

4 ответа

Решение

Для записи я наконец-то заставил это работать. Выяснилось, что трудности, с которыми я столкнулся, были связаны с проблемами сортировки строк. Я должен был быть более точным в моем импорте функций win32.

Ниже приведен код, который создаст пользовательский класс окна в C# - полезно для поддержки старых API, которые вы можете иметь, полагаясь на пользовательские классы окна.

Он должен работать в WPF или Winforms, пока в потоке работает насос сообщений.

РЕДАКТИРОВАТЬ: Обновлено, чтобы исправить сообщенный сбой из-за раннего сбора делегата, который оборачивает обратный вызов. Делегат теперь сохраняется как член, а делегат явно маршалируется как указатель на функцию. Это устраняет проблему и облегчает понимание поведения.

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;
}

Я хотел бы прокомментировать ответ 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
    );
}

В конструкторе, который я скопировал выше, есть небольшая ошибка: экземпляр WNDCLASS создается, но не сохраняется. В конечном итоге это будет мусор. Но WNDCLASS содержит делегата WndProc. Это приводит к ошибке, как только WNDCLASS является сборщиком мусора. Экземпляр WNDCLASS должен храниться в переменной-члене до тех пор, пока окно не будет уничтожено.

1) Вы можете просто создать подкласс для обычного класса Windows Forms... нет необходимости во всех этих вызовах win32, вам просто нужно разобрать сообщение WndProc вручную... и все.

2) Вы можете импортировать пространство имен System.Windows.Forms и использовать его вместе с WPF. Я считаю, что проблем не будет, если вы не переплетаете слишком много оконных форм в своем приложении WPF. Вы просто хотите создать свою скрытую форму, чтобы получить сообщение, верно?

Пример подкласса 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);
}

Поскольку вы уже знаете RegisterClass и все эти вызовы Win32, я предполагаю, что сообщение WndProc не будет проблемой для вас...

WNDCLASS wind_class; поместите определение в класс, а не функцию, и сбой будет исправлен.

Другие вопросы по тегам