Регистрация пользовательского класса окна Win32 из С#

StackOverflow https://stackoverflow.com/questions/128561

  •  02-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

Сделав это, я могу успешно разместить стандартное окно Win32, например.«список» внутри WPF.Однако когда я вызываю CreateWindowEx для своего пользовательского окна, он всегда возвращает значение null.

Мой призыв к RegisterClass добился успеха, но я не уверен, на что я должен установить член WNCLASS.LPFNWNDPROC.

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

Это было полезно?

Решение

Для справки, я наконец-то заставил это работать.Оказалось, что трудности, с которыми я столкнулся, сводились к проблемам с сортировкой струн.Мне пришлось быть более точным при импорте функций 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;
}

Другие советы

Я хотел бы прокомментировать ответ моречили:

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 ветер_класс;поместите определение в класс, а не в функцию, и сбой будет исправлен.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top