Registering a custom win32 window class from c#

19,875

Solution 1

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.

EDIT: Updated to fix the reported crash due to early collection of the delegate that wraps the callback. The delegate is now held as a member and the delegate explicitly marshaled as a function pointer. This fixes the issue and makes it easier to understand the behaviour.

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

Solution 2

I'd like to comment the answer of 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
    );
}

In the constructor I copied above is slight error: The WNDCLASS instance is created, but not saved. It will eventually be garbage collected. But the WNDCLASS holds the WndProc delegate. This results in an error as soon as WNDCLASS is garbage collected. The instance of WNDCLASS should be hold in a member variable until the window is destroyed.

Share:
19,875
Aleadam
Author by

Aleadam

Always learning

Updated on June 05, 2022

Comments

  • Aleadam
    Aleadam almost 2 years

    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?

  • Aleadam
    Aleadam over 15 years
    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?
  • crdx
    crdx about 13 years
    This works fine on 32bit windows, but crashes on 64bit. I'm having trouble debugging why, because the piece of code that crashes is nothing to do with the custom window. (It happens when I set Visible = false in an event handler of another form that has nothing to do with this class, but when I don't instantiate it, it doesn't crash.) Do you by any chance have any idea why it might be, or any direction towards which you can point me?
  • chakrit
    chakrit almost 11 years
    @morechill you can set the class name for your forms when using winforms.
  • Aleadam
    Aleadam almost 11 years
    Thanks for the update. I'm no longer doing .net development but would be happy to update my responses if you can provide a reliable working example.
  • Aleadam
    Aleadam almost 11 years
    Thanks - I've hopefully fixed the example with a similar change - I manage the delegate lifetime rather than the WNDCLASS.
  • Aleadam
    Aleadam almost 11 years
    Thanks - I've hopefully fixed the example with a similar change - I manage the delegate lifetime rather than the WNDCLASS.
  • Ready Cent
    Ready Cent over 9 years
    I feel like an idiot but I can't get this to work. I am using windows forms and creating a form with a button click and calling Show(). I am wrapping that in using (CustomWindow cw = new CustomWindow("waffle")) { ... } In the class that inherits from form I am doing protected override CreateParams CreateParams { get { CreateParams param = base.CreateParams; param.ClassName = "waffle"; return param; } } But I still get the error "Window class name is not valid."
  • Aleadam
    Aleadam over 9 years
    Hi - I'm not sure exactly what you are trying to achieve - can you explain? The above code is intended to create a window with a custom window class name. This can be hosted in a winforms application but the window itself will not wrapped in an instance of a Form object. It will however be able to respond to any messages sent to it. It sounds like you might be trying to create a Form object that uses a custom window class - that's a different thing and beyond the scope of this solution. Does that help?
  • Guavaman
    Guavaman over 9 years
    Thanks for this! Saved me a lot of trial and error. However, the IDisposable implementation should have a couple of additions. The destructor: ~CustomWindow() { Dispose(false); } And inside Dispose, you need to add m_disposed = true;