Регистрация пользовательских оконных классов 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)

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

 morechilli09 авг. 2013 г., 20:23
Спасибо - ямы надеемся исправить пример с похожим изменением - я управляю временем жизни делегата, а не WNDCLASS.

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

 morechilli09 авг. 2013 г., 20:24
Спасибо - ямы надеемся исправить пример с похожим изменением - я управляю временем жизни делегата, а не 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 неэто не проблема для вас ...

 morechilli24 сент. 2008 г., 22:55
Спасибо за предложение, но яЯ не уверен, что это решает мою проблему. Мне нужно, чтобы класс окна имел определенное имя, соответствующее старому API. Я не'Думаешь, ты мог бы установить имя класса в winforms?
 chakrit09 июл. 2013 г., 06:49
@morechill вы можете установить имя класса для ваших форм при использовании winforms.
 morechilli10 июл. 2013 г., 12:22
Спасибо за обновление. Я'Я больше не занимаюсь разработкой .net, но был бы рад обновить мои ответы, если вы можете предоставить надежный рабочий пример.
Решение Вопроса

что трудности, с которыми я столкнулся, были связаны с проблемами сортировки строк. Я должен был быть более точным в моем импорте функций 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;
}
 morechilli09 авг. 2013 г., 20:22
Теперь должно быть исправлено.
 Antoine Aubry29 апр. 2010 г., 20:55
+1 для обмена кодом. Спасибо!
 Guavaman18 окт. 2014 г., 10:48
Спасибо за это! Спасло меня много проб и ошибок. Однако реализация IDisposable должна иметь несколько дополнений. Деструктор: ~ CustomWindow () {Dispose (false); } А внутри Dispose вам нужно добавить m_disposed = true; I '
 crdx12 апр. 2011 г., 01:02
Это прекрасно работает на 32-битных окнах, но вылетает на 64-битных. Я'У меня возникли проблемы с отладкой, почему, потому что фрагмент кода, который вылетает, не имеет ничего общего с пользовательским окном. (Это происходит, когда я устанавливаю Visible = false в обработчике событий другой формы, который не имеет ничего общего с этим классом, но когда я неинстанцировать это, это неt аварии.) Есть ли у вас случайно какие-либо идеи, почему это может быть, или любое направление, на которое вы можете указать мне?
 Ready Cent10 сент. 2014 г., 16:10
Я чувствую себя идиотом, но могуне заставить это работать. Я использую формы Windows и создаю форму нажатием кнопки и вызываю Show (). Я оборачиваю это в использовании (CustomWindow cw = new CustomWindow ("вафельный»)) {...} В классе, который наследует от формы, я делаю защищенное переопределение CreateParams CreateParams {get {CreateParams param = base.CreateParams; param.ClassName = "вафельный»; возвратный параметр; }} Но я все еще получаю ошибку "Неверное имя класса окна. "
 morechilli11 сент. 2014 г., 13:46
Привет яЯ не уверен, что именно вы пытаетесь достичь - вы можете объяснить? Приведенный выше код предназначен для создания окна с именем класса пользовательского окна. Это может быть размещено в приложении winforms, но само окно не будет перенесено в экземпляр объекта Form. Однако он сможет отвечать на любые отправленные ему сообщения. Похоже, вы пытаетесь создать объект Form, который использует собственный класс окна - этоЭто другое дело и выходит за рамки этого решения. Это помогает?

Ваш ответ на вопрос