2008-09-24 12 views
11

Tengo una nueva aplicación escrita en WPF que necesita admitir una antigua API que le permite recibir un mensaje que se ha publicado en una ventana oculta. Normalmente, otra aplicación utiliza FindWindow para identificar la ventana oculta utilizando el nombre de su clase de ventana personalizada.Registro de una clase de ventana Win32 personalizada desde C#

1) Supongo que para implementar una clase de ventana personalizada ¿Necesito usar las llamadas de la vieja escuela de win32?

Mi aplicación C++ antigua usa RegisterClass y CreateWindow para hacer la ventana invisible más simple posible.

Creo que debería poder hacer lo mismo dentro de C#. No quiero que mi proyecto tenga que compilar ningún código no administrado.

He intentado heredar de System.Windows.Interop.HwndHost y usar System.Runtime.InteropServices.DllImport para obtener los métodos API anteriores.

Haciendo esto puedo alojar con éxito una ventana win32 estándar, p. Ej. "listbox" dentro de WPF. Sin embargo, cuando invoco CreateWindowEx para mi ventana personalizada, siempre devuelve null.

Mi llamada a RegisterClass tiene éxito, pero no estoy seguro de a qué debería estar configurando el miembro WNDCLASS.lpfnWndProc.

2) ¿Alguien sabe cómo hacer esto con éxito?

Respuesta

31

Para el registro Finalmente conseguí que esto funcionara. Resultó que las dificultades que tuve se debieron a problemas de clasificación de cuerdas. Tenía que ser más preciso en la importación de funciones win32.

A continuación se muestra el código que creará una clase de ventana personalizada en C#, útil para admitir antiguas API que pueda tener que dependan de clases de ventana personalizadas.

Debería funcionar en WPF o en Winforms siempre que se ejecute una bomba de mensajes en la secuencia.

EDIT: Se ha actualizado para corregir el bloqueo informado debido a la recopilación anticipada del delegado que ajusta la devolución de llamada. El delegado ahora se mantiene como miembro y el delegado se calcula explícitamente como un puntero a la función. Esto soluciona el problema y facilita la comprensión del comportamiento.

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 para compartir el código. ¡Gracias! –

+1

Esto funciona bien en las ventanas de 32 bits, pero se bloquea en 64 bits. Tengo problemas para depurar el motivo, porque el fragmento de código que falla no tiene nada que ver con la ventana personalizada. (Sucede cuando establezco Visible = falso en un controlador de eventos de otro formulario que no tiene nada que ver con esta clase, pero cuando no lo instancia, no se cuelga.) ¿Tiene alguna posibilidad de tener alguna idea de por qué? podría ser, o cualquier dirección hacia la que me puedas señalar? – crdx

+0

Ahora debería arreglarse. – morechilli

0

1) Puede simplemente una subclase de una clase normal de Windows Forms ... sin necesidad de que todas esas llamadas Win32, sólo tiene que analizar el mensaje WndProc manualmente ... es todo.

2) Puede importar el espacio de nombres System.Windows.Forms y utilizarlo junto con WPF, creo que no habrá ningún problema, siempre y cuando no se entrelazan demasiado formas de Windows en la aplicación de WPF. Solo quiere crear una instancia de su formulario oculto personalizado para recibir un mensaje, ¿verdad?

ejemplo de WndProc subclases:

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); 
} 

Como usted ya sabe RegisterClass y todos aquellos llamadas de Win32, que asume el mensaje WndProc no sería un problema para usted ...

+0

Gracias por la sugerencia, pero no estoy seguro de que resuelva mi problema. Necesito que la clase ventana tenga un nombre específico para que coincida con la antigua API. No creía que pudieras establecer el nombre de clase en winforms? – morechilli

+0

@morechill puede establecer el nombre de la clase para sus formularios cuando usa winforms. – chakrit

+0

Gracias por la actualización. Ya no estoy desarrollando .net, pero me gustaría actualizar mis respuestas si puede proporcionar un ejemplo de trabajo confiable. – morechilli

-1

WNDCLASS wind_class; ponga la definición en la clase, no en la función, y se solucionará el bloqueo.

+0

Gracias. Espero haber arreglado el ejemplo con un cambio similar. Administro el tiempo de vida del delegado en lugar del WNDCLASS. – morechilli

0

me gustaría comentar la respuesta de 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 
    ); 
} 

En el constructor He copiado anteriormente es ligero error: Se crea la instancia WNDCLASS, pero no se guarda. Eventualmente será basura recolectada. Pero el WNDCLASS tiene el delegado de WndProc. Esto produce un error tan pronto como WNDCLASS es basura recolectada. La instancia de WNDCLASS debe mantenerse en una variable miembro hasta que se destruya la ventana.

+0

Gracias. Espero haber arreglado el ejemplo con un cambio similar. Administro la vida del delegado en lugar de WNDCLASS. – morechilli

Cuestiones relacionadas