I'm currently writing custom game engine (for self education) and working on window system. I need to create basic OS window (so I can later link it as a device for DirectX or to create context for OpenGL, etc). I found an example of creating Win32 window with C# so I used it as a prototype (example code is working properly on my machine), but the source was made like a separate program, and I need to implement it as a class, from which i can get an IntPtr to created window.

I wrote my own Win32Window class, implementing all required features. Then, I used another project to test it out (i'm writing this class as a part of my gameengine dll), but it fails to create window. Another questions (on Stackoverflow and other forums) that touch this topic, have different issues that come from using winApi features i don't need (like controls and other stuff) or incorrect order of registering window class and creating window.

As the title says, I found out that the CreateWindowEx returns null.

Marshal.GetLastWin32Error says that CreateWindowEx cannot find registered class.

I tried to use variable string in the place of ClassName, so i dont missprint the name.

I tried to use overloaded version of CreateWindowEx with IntPtr to registered class.

Neither of them worked.

All the managed code for WinApi was taken from Pinvoke.com and MSDN documentation.

The problem is definetly not caused by not giving an actual hInstance pointer to RegisterClassEx and CreateWindowEx instead of IntPtr.Zero.

Here is window class code:

public sealed class Win32Window : NativeWindow // NativeWindow is my engine's window API containing some size, name and pointer properties
    {
          -- Some properties and fields routine -- 

        //Constructor takes job of registering class
        public Win32Window(string WindowName, WndProc CallBack)
        {
            this.WindowName = WindowName;
            this.Callback = CallBack;
            this.wc = WNDCLASSEX.Build();
            this.wc.style = (int)(CS.HRedraw | CS.VRedraw);
            this.wc.lpfnWndProc = this.Callback;
            this.wc.hInstance = IntPtr.Zero;
            this.wc.hCursor = LoadCursor(IntPtr.Zero, (int)IDC.Arrow);
            this.wc.hbrBackground = IntPtr.Zero;
            this.wc.lpszClassName = ClassName;
            this.wc.cbClsExtra = 0;
            this.wc.cbWndExtra = 0;
            this.wc.hIcon = LoadIcon(IntPtr.Zero,(IntPtr)IDI_APPLICATION);
            this.wc.lpszMenuName = null;
            this.wc.hIconSm = IntPtr.Zero;

            ClassPtr = (IntPtr)RegisterClassEx(ref this.wc);
            Console.WriteLine(ClassPtr); //Outputs negative integer, so i can conclude this part works properly
    }       
    public void Create()
    {
            this.WindowHandle = CreateWindowEx(0,
                        ClassName,
                this.WindowName,
                (uint)WS.OverlappedWindow,
                this.PosX,
                this.PosY,
                this.Width,
                this.Height,
                IntPtr.Zero,
                IntPtr.Zero,
                IntPtr.Zero,
                IntPtr.Zero);
            Console.WriteLine($"{WindowHandle == IntPtr.Zero}  {Marshal.GetLastWin32Error()}");  //Outputs "True  1407" 
    }

    public void Show()
    {
        ShowWindow(this.WindowHandle, 1);
    }

    public void Iterate()
    {
        while (GetMessage(out msg, IntPtr.Zero, 0, 0) > 0)
        {
            TranslateMessage(ref msg);
            DispatchMessage(ref msg);
        }

      --- Some [DllImport] routine ---
    }

Here is TestWindow class code:

public class TestWindow
    {
        Win32Window.WndProc callback;

        Win32Window Window;

        private static IntPtr WndProc(IntPtr hWnd, Win32Window.WM message, IntPtr wParam, IntPtr lParam)
        {
            Console.WriteLine(message);
            switch (message)
            {
                case Win32Window.WM.Destroy:
                    Win32Window.PostQuitMessage(0);
                    return IntPtr.Zero;
                default:
                    return (Win32Window.DefWindowProc(hWnd, message, wParam, lParam));
            }
        }

        public TestWindow()
        {
            callback = WndProc;

            Window = new Win32Window("TestWindow", callback);
            Window.Create();
            Window.Show();
            Window.Iterate();
        }
    }

The Main method of test console app is just creating a new instance of TestWindow.

1 Answers

-1
Rita Han - MSFT On Best Solutions

You may map string in C# to LPCWSTR in C++ for lpClassName parameter of CreateWindowEx(). They are not equal.

- One solution:

Refer to @dan04's answer:

C# uses UTF-16 strings, so you'll want to prefer the "W" version of these functions. Use PdhOpenQueryW. Then the first parameter has C++ type const wchar_t*. The C# type is [MarshalAs(UnmanagedType.LPWStr)] string.

Try the following code:

    delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    class Win32Window
    {
        const UInt32 WS_OVERLAPPEDWINDOW = 0xcf0000;
        const UInt32 WS_VISIBLE = 0x10000000;
        const UInt32 CS_USEDEFAULT = 0x80000000;
        const UInt32 CS_DBLCLKS = 8;
        const UInt32 CS_VREDRAW = 1;
        const UInt32 CS_HREDRAW = 2;
        const UInt32 COLOR_WINDOW = 5;
        const UInt32 COLOR_BACKGROUND = 1;
        const UInt32 IDC_CROSS = 32515;
        const UInt32 WM_DESTROY = 2;
        const UInt32 WM_PAINT = 0x0f;
        const UInt32 WM_LBUTTONUP = 0x0202;
        const UInt32 WM_LBUTTONDBLCLK = 0x0203;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct WNDCLASSEX
        {
            [MarshalAs(UnmanagedType.U4)]
            public int cbSize;
            [MarshalAs(UnmanagedType.U4)]
            public int style;
            public IntPtr lpfnWndProc;
            public int cbClsExtra;
            public int cbWndExtra;
            public IntPtr hInstance;
            public IntPtr hIcon;
            public IntPtr hCursor;
            public IntPtr hbrBackground;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpszMenuName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpszClassName;
            public IntPtr hIconSm;
        }


        private WndProc delegWndProc = myWndProc;

        [DllImport("user32.dll")]
        static extern bool UpdateWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
        static extern bool DestroyWindow(IntPtr hWnd);


        [DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowExW")]
        public static extern IntPtr CreateWindowExW(
           int dwExStyle,
           [MarshalAs(UnmanagedType.LPWStr)]
           string lpClassName,
           [MarshalAs(UnmanagedType.LPWStr)]
           string lpWindowName,
           UInt32 dwStyle,
           int x,
           int y,
           int nWidth,
           int nHeight,
           IntPtr hWndParent,
           IntPtr hMenu,
           IntPtr hInstance,
           IntPtr lpParam);

        [DllImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
        static extern System.UInt16 RegisterClassExW([In] ref WNDCLASSEX lpWndClass);

        [DllImport("kernel32.dll")]
        static extern uint GetLastError();

        [DllImport("user32.dll")]
        static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        static extern void PostQuitMessage(int nExitCode);

        [DllImport("user32.dll")]
        static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);


        internal bool create()
        {
            WNDCLASSEX wind_class = new WNDCLASSEX();
            wind_class.cbSize = Marshal.SizeOf(typeof(WNDCLASSEX));
            wind_class.style = (int)(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS);
            wind_class.hbrBackground = (IntPtr)COLOR_BACKGROUND + 1;
            wind_class.cbClsExtra = 0;
            wind_class.cbWndExtra = 0;
            wind_class.hInstance = Marshal.GetHINSTANCE(this.GetType().Module);
            wind_class.hIcon = IntPtr.Zero;
            wind_class.hCursor = LoadCursor(IntPtr.Zero, (int)IDC_CROSS);
            wind_class.lpszMenuName = null;
            wind_class.lpszClassName = "myClass";
            wind_class.lpfnWndProc = Marshal.GetFunctionPointerForDelegate(delegWndProc);
            wind_class.hIconSm = IntPtr.Zero;
            ushort regResult = RegisterClassExW(ref wind_class);

            if (regResult == 0)
            {
                uint error = GetLastError();
                return false;
            }


            IntPtr hWnd = CreateWindowExW(0, wind_class.lpszClassName, "Hello Win32", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 300, 400, IntPtr.Zero, IntPtr.Zero, wind_class.hInstance, IntPtr.Zero);

            Console.WriteLine($"{hWnd == IntPtr.Zero}  {Marshal.GetLastWin32Error()}");   
            if (hWnd == ((IntPtr)0))
            {
                return false;
            }
            ShowWindow(hWnd, 1);
            UpdateWindow(hWnd);
            return true;
        }

        private static IntPtr myWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
        {
            switch (msg)
            {
                // All GUI painting must be done here
                case WM_PAINT:
                    break;

                case WM_DESTROY:
                    DestroyWindow(hWnd);
                    break;

                default:
                    break;
            }
            return DefWindowProc(hWnd, msg, wParam, lParam);
        }
    }

Update:

Note: Thanks @IInspectable for remind. Edit the code to use Unicode API like RegisterClassExW and CreateWindowExW for consistency in my answer.

But I am not suggesting to use Unicode API in new Windows application. Instead, for all functions with text arguments, applications should normally use the generic function prototypes and define UNICODE to compile the functions into Unicode functions.

Refer to:

Unicode in the Windows API, Conventions for Function Prototypes, Default Marshaling for Strings

- Another solution:

The parameter lpClassName of CreateWindowEx() also accept a class atom created by a previous call to the RegisterClass or RegisterClassEx function. So if the RegisterClassEx() successes you can use its return value (ATOM) as the replacement of the class name in CreateWindowExW() to see if it works. In C++, it will like this:

ATOM myClassAtom = RegisterClassExW(&wcex);
HWND hWnd = CreateWindowEx(0, (LPCWSTR)myClassAtom, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

In C#, based on above C# sample, use UInt16 replace ATOM, it will like this:

//...

            [DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowExW")]
            public static extern IntPtr CreateWindowExW(
               int dwExStyle,
               UInt16 lpClassName, // <---
               [MarshalAs(UnmanagedType.LPWStr)]
               string lpWindowName,
               UInt32 dwStyle,
               int x,
               int y,
               int nWidth,
               int nHeight,
               IntPtr hWndParent,
               IntPtr hMenu,
               IntPtr hInstance,
               IntPtr lpParam);

//...

                UInt16 regResult = RegisterClassExW(ref wind_class);

                if (regResult == 0)
                {
                    uint error = GetLastError();
                    return false;
                }


                IntPtr hWnd = CreateWindowExW(0, regResult, "Hello Win32", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 300, 400, IntPtr.Zero, IntPtr.Zero, wind_class.hInstance, IntPtr.Zero);