views:

2920

answers:

3

I have a new application written in WPF that needs to support an old API that allows it to receive a message that has been posted to a hidden window. Typically another application uses FindWindow to identify the hidden window using the name of its custom window class.

1) I assume to implement a custom window class I need to use old school win32 calls?

My old c++ application used RegisterClass and CreateWindow to make the simplest possible invisible window.

I believe I should be able to do the same all within c#. I don't want my project to have to compile any unmanaged code.

I have tried inheriting from System.Windows.Interop.HwndHost and using System.Runtime.InteropServices.DllImport to pull in the above API methods.

Doing this I can successfully host a standard win32 window e.g. "listbox" inside WPF. However when I call CreateWindowEx for my custom window it always returns null.

My call to RegisterClass succeeds but I am not sure what I should be setting the WNDCLASS.lpfnWndProc member to.

2) Does anyone know how to do this successfully?

A: 

1) You can just subclass a normal Windows Forms class... no need for all those win32 calls, you just need to parse the WndProc message manually... is all.

See this tutorial and this kb article

2) You can import the System.Windows.Forms namespace and use it alongside WPF, I believe there won't be any problems as long as you don't intertwine too much windows forms into your WPF application. You just want to instantiate your custom hidden form to receieve a message is that right?

example of WndProc subclassing:

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

Since you already know RegisterClass and all those Win32 calls, I assume the WndProc message wouldn't be a problem for you...

chakrit
Thanks for the suggestion but I'm not sure it solves my problem. I need the window class to have a specific name to match the old API. I didn't think you could set the class name in winforms?
morechilli
+4  A: 

For the record I finally got this to work. Turned out the difficulties I had were down to string marshalling problems. I had to be more precise in my importing of win32 functions.

Below is the code that will create a custom window class in c# - useful for supporting old APIs you might have that rely on custom window classes.

It should work in either WPF or Winforms as long as a message pump is running on the thread.

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 WndProc 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");

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

    private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) 
    {
        return DefWindowProcW(hWnd, msg, wParam, lParam);
    }
}
morechilli
+1 for sharing the code. Thanks!
Antoine Aubry
A: 

I have the same problem as morechilli... except I can't get his solution to work because I don't know how to properly make use of it. When I try, I get a "CallbackOnCollectedDelegate" exception... apparently I am unable to keep the delegate code in memory. I don't understand why... the actual method is a class method, and the class instance variable is static as well. I would not expect the class to be garbage collected until the Window is closed. Can anyone help me to make use of morechilli's generously provided solution? Thanks in advance.

public partial class MainWindow : Window
{

    static CustomWindow win = null;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void btnCreateWindow_Click(object sender, RoutedEventArgs e)
    {
        win = new CustomWindow("TestClassName");
    }
}
AlexF
Hi - my first piece of advice would be to fix up the code to follow the IDispose pattern - MainWindow must dispose "win" when it is destroyed. Correct use of dispose normally fixes early collection problems - hope this helps.
morechilli