1

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.

  • The error message seems clear. Try debugging. Check that the class name you use when creating matches the name used when you register. – David Heffernan Apr 29 '19 at 21:40

2 Answers2

0

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);
Rita Han
  • 9,574
  • 1
  • 11
  • 24
  • I don't think there is any way that you can diagnose the cause of the failure to be erroneous text encoding. – David Heffernan Apr 30 '19 at 11:08
  • 1
    This is introducing so many character encoding mismatches, which are later fixed up, with new failure opportunities. Unless you absolutely need your code to run on Windows 95/98/ME, the recommendation is simple: Use `CharSet.Unicode` **everywhere**, and import the Unicode versions of the Windows API (usually with a trailing `W` in the function name). – IInspectable Apr 30 '19 at 11:08
  • 1. The real problem was that I should have gotten the hInstance pointer from inside the module (just like in your example; this is probably the case because not the application, but dll starts the window) 2. It doesn't really matter if you marshal string inside the WNDCLASS struct - you should be sure to do it in declaration of CreateWindowEx() (just tested this out, in case someone else will need it in the future) – Serg Cherno Apr 30 '19 at 11:15
  • *"I am not suggesting to use Unicode API in new Windows application."* - That's odd. You are using UTF-16LE already, exclusively, when writing C# code. That's what [String](https://learn.microsoft.com/en-us/dotnet/api/system.string?view=netframework-4.8) uses internally, and you do not even get an option to change that. Likewise, the Windows API is almost exclusively exposed as a Unicode interface, with the ANSI exports being wrappers. It's unclear what problem you are trying to solve by allowing clients of your code the flexibility to choose an option they do not want. – IInspectable May 03 '19 at 08:52
  • It seems there is some misunderstanding. First I mean it is suggested to use API for example like RegisterClassEx ([A generic version that can be compiled for either Windows code pages or Unicode.](https://learn.microsoft.com/en-us/windows/desktop/intl/unicode-in-the-windows-api)) instead of RegisterClassExW in code, which function prototype to select to be determined by compile condition. Some newer functions support only Unicode versions, for example, LCIDToLocaleName and LocaleNameToLCID, without a letter 'W' postfix. Second I am talking about C++ native API not C# API. – Rita Han May 03 '19 at 09:51
  • Unless you are targeting Win95/98/ME, there really isn't any reason to use the ANSI version of any Windows API call. The ANSI versions are as generic as you think: They are wrappers around the Unicode implementations, translating prior to the call, as well as just before returning. That needlessly wastes CPU cycles, and - worse - imposes arbitrary limits on arguments, as well as introduces opportunities for failure. I don't even know what happens when you use a Unicode input argument that cannot be represented on any given ANSI codepage. Besides, `RegisterClassEx` is a macro, not an API. – IInspectable May 03 '19 at 12:24
  • The generic version is the version without 'A' or 'W' trailing as indicate in the [document](https://learn.microsoft.com/en-us/windows/desktop/intl/unicode-in-the-windows-api). – Rita Han May 06 '19 at 07:13
  • That's not a function, that's exported anywhere. That's a preprocessor symbol. And it's something that no one has had any need to use for well over a decade now. Not only are you making it easy for your code to fail today, you are also planting a time bomb, waiting to explode at some point. Windows has started shipping experimental support for UTF-8, reusing the ANSI API names. Regardless, specifying `CharSet.Ansi`, but using the `MarshalAs(UnmanagedType.LPWStr)` attribute on every string field is certainly not standard practice. – IInspectable May 06 '19 at 18:53
0

We have many executables that link to a number of different DLLs (that we write) that are in C++, C# and C++/CLI. All of a sudden I started getting error 1407 in an application of ours which only occurred when the program was ran within the Visual Studio 2015 Debugger.

I finally managed to fix the problem by changing the Debugger Type option to Mixed after it had been changed to "Native Only"

For some reason changing back to "Native Only" does not then cause the error 1407 error to come back.

Hope this helps anyone whose problem is not related to the answer above

Peter Nimmo
  • 1,045
  • 2
  • 12
  • 25