Регистрация пользовательских оконных классов win32 из c # - PullRequest
12 голосов
/ 24 сентября 2008

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

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

Мое старое приложение на c ++ использовало RegisterClass и CreateWindow для создания максимально простого невидимого окна.

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

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

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

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

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

Ответы [ 4 ]

33 голосов
/ 26 сентября 2008

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

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

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

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

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;
}
1 голос
/ 05 августа 2013

Я бы хотел прокомментировать ответ 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 должен храниться в переменной-члене до тех пор, пока окно не будет уничтожено.

0 голосов
/ 04 января 2013

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

0 голосов
/ 24 сентября 2008

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

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 не будет для вас проблемой ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...