2008-09-24 1 views
11

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

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

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

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

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

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

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

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

ответ

31

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

+1 для обмена кодом. Благодаря! – 2010-04-29 18:55:58

0

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

+0

Спасибо за предложение, но я не уверен, что он решает мою проблему. Мне нужно, чтобы класс окна имел определенное имя, соответствующее старому API. Я не думал, что вы можете установить имя класса в winforms? – morechilli 2008-09-24 20:55:59

+0

@morechill вы можете установить имя класса для своих форм при использовании winforms. – chakrit 2013-07-09 04:49:47

+0

Спасибо за обновление. Я больше не занимаюсь разработкой .net, но буду рад обновить свои ответы, если вы сможете предоставить надежный рабочий пример. – morechilli 2013-07-10 10:22:28

-1

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

0

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