Pregunta

Tengo una nueva aplicación escrita en WPF que necesita admitir una API antigua 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 usando el nombre de su clase de ventana personalizada.

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

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

Creo que debería poder hacer lo mismo todo 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 extraer los métodos API anteriores.

Al hacer esto, puedo alojar con éxito una ventana estándar de win32, por ejemplo. " cuadro de lista " Dentro de WPF. Sin embargo, cuando llamo a CreateWindowEx para mi ventana personalizada, siempre devuelve nulo.

Mi llamada a RegisterClass tiene éxito pero no estoy seguro de lo que debería estar configurando Miembro WNDCLASS.lpfnWndProc para.

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

¿Fue útil?

Solución

Para que conste, finalmente conseguí que esto funcionara. Resultó que las dificultades que tenía se debían a problemas de ordenación de cadenas. Tuve que ser más preciso en mi 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 API antiguas que podría tener y que dependen de clases de ventana personalizadas.

Debería funcionar en WPF o en Winforms siempre que se esté ejecutando un mensaje de bomba en el hilo.

EDITAR: Actualizado para corregir el fallo informado debido a la recopilación anticipada del delegado que envuelve la devolución de llamada. El delegado ahora se mantiene como miembro y el delegado se calcula de forma explícita como un puntero de 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;
}

Otros consejos

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 que copié arriba hay un ligero error: la instancia WNDCLASS se crea, pero no se guarda. Eventualmente será recogida de basura. Pero el WNDCLASS tiene el delegado WndProc. Esto produce un error tan pronto como WNDCLASS se recolecta como basura. La instancia de WNDCLASS debe mantenerse en una variable miembro hasta que se destruya la ventana.

1) Puede crear una subclase de una clase de Windows Forms normal ... no es necesario realizar todas esas llamadas win32, solo necesita analizar el mensaje WndProc manualmente ... eso es todo.

2) Puedes importar el espacio de nombres System.Windows.Forms y usarlo junto con WPF, creo que no habrá ningún problema siempre y cuando no entrelazes demasiados formularios de Windows en su aplicación WPF. Solo desea crear una instancia de su formulario oculto personalizado para recibir un mensaje, ¿verdad?

ejemplo de subclasificación 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);
}

Como ya sabes RegisterClass y todas esas llamadas a Win32, asumo que el mensaje WndProc no sería un problema para ti ...

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top