Domanda

Ho una nuova applicazione scritta in WPF che deve supportare una vecchia API che le consente di ricevere un messaggio che è stato pubblicato in una finestra nascosta. In genere un'altra applicazione utilizza FindWindow per identificare la finestra nascosta utilizzando il nome della sua classe di finestra personalizzata.

1) Presumo che per implementare una classe di finestre personalizzate, devo usare le chiamate win32 della vecchia scuola?

La mia vecchia applicazione c ++ utilizzava RegisterClass e CreateWindow per rendere la finestra invisibile più semplice possibile.

Credo che dovrei essere in grado di fare lo stesso in c #. Non voglio che il mio progetto debba compilare alcun codice non gestito.

Ho provato a ereditare da System.Windows.Interop.HwndHost e utilizzando System.Runtime.InteropServices.DllImport per estrarre i metodi API precedenti.

In questo modo posso ospitare con successo una finestra standard win32 ad es. & Quot; listbox " all'interno del WPF. Tuttavia, quando chiamo CreateWindowEx per la mia finestra personalizzata, restituisce sempre null.

La mia chiamata a RegisterClass ha esito positivo ma non sono sicuro di cosa dovrei impostare Membro WNDCLASS.lpfnWndProc a.

2) Qualcuno sa come farlo con successo?

È stato utile?

Soluzione

Per la cronaca ho finalmente fatto funzionare tutto questo. Ho scoperto che le difficoltà che ho avuto erano dovute a problemi di marshalling delle stringhe. Dovevo essere più preciso nella mia importazione di funzioni win32.

Di seguito è riportato il codice che creerà una classe di finestra personalizzata in c #, utile per supportare vecchie API che potresti avere che si basano su classi di finestre personalizzate.

Dovrebbe funzionare in WPF o Winforms fintanto che un thread di messaggi è in esecuzione sul thread.

EDIT: Aggiornato per correggere l'arresto anomalo segnalato a causa della raccolta anticipata del delegato che avvolge il callback. Il delegato è ora tenuto come membro e il delegato è stato esplicitamente sottoposto a marshalling come puntatore a funzione. Questo risolve il problema e semplifica la comprensione del comportamento.

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

Altri suggerimenti

Vorrei commentare la risposta di 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
    );
}

Nel costruttore che ho copiato sopra c'è un leggero errore: l'istanza di WNDCLASS è stata creata, ma non salvata. Alla fine sarà spazzatura raccolta. Ma WNDCLASS detiene il delegato WndProc. Ciò provoca un errore non appena WNDCLASS viene raccolto. L'istanza di WNDCLASS deve essere mantenuta in una variabile membro fino a quando la finestra non viene distrutta.

1) Puoi semplicemente sottoclassare una normale classe di Windows Form ... non c'è bisogno di tutte quelle chiamate win32, devi solo analizzare il messaggio WndProc manualmente ... tutto qui.

2) È possibile importare lo spazio dei nomi System.Windows.Forms e utilizzarlo insieme a WPF, credo che non ci saranno problemi finché non si intrecciano troppi moduli di Windows in la tua applicazione WPF. Vuoi solo creare un'istanza del tuo modulo nascosto personalizzato per ricevere un messaggio, giusto?

esempio di sottoclasse 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);
}

Dato che conosci già RegisterClass e tutte quelle chiamate Win32, suppongo che il messaggio WndProc non sia un problema per te ...

WNDCLASS wind_class; inserisci la definizione nella classe, non la funzione, e l'arresto anomalo verrà corretto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top