2008-09-24 59 views
11

我有一個新的應用程序在WPF中編寫,需要支持一箇舊的API,它允許它接收已發佈到隱藏窗口的消息。通常,另一個應用程序使用FindWindow使用其自定義窗口類的名稱來標識隱藏窗口。註冊一個自定義的win32窗口類從c#

1)我想實現一個自定義窗口類,我需要使用老同學Win32調用?使用的RegisterClass和CreateWindow的,使盡可能簡單的無形窗口

我的老C++應用程序。

我相信我應該可以在c#中完成相同的操作。我不希望我的項目必須編譯任何非託管代碼。

我已經嘗試從System.Windows.Interop.HwndHost繼承並使用System.Runtime.InteropServices.DllImport來引入上述API方法。

這樣做,我可以成功地舉辦一個標準的win32窗口,例如「列表框」內WPF。 但是,當我爲我的自定義窗口調用CreateWindowEx時,它始終返回null。

我給RegisterClass的調用成功了,但我不確定我應該如何設置 WNDCLASS.lpfnWndProc成員。

2)有誰知道如何成功地做到這一點?

回答

31

爲了記錄我終於得到了這個工作。 發現我遇到的困難是串串編組問題。 我必須更加精確地導入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; 
} 
+1

+1分享代碼。謝謝! – 2010-04-29 18:55:58

0

1)你可以繼承一個普通的Windows窗體類...不需要所有的這些win32調用,你只需要手動解析WndProc消息...就這樣了。

2)可以導入System.Windows.Forms命名空間,並使用它旁邊WPF,我相信不會有任何問題,只要你不要糾結太多Windows窗體到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的實例應該保存在一個成員變量中,直到窗口被銷燬。