尝试在自定义 c# window class 中实现 WinApi 时,如何修复 windows 错误 1407(找不到 window class)?

How to fix windows error 1407 (Cannot find window class) when trying to implement WinApi in custom c# window class?

我目前正在编写自定义游戏引擎(用于自学)并致力于 window 系统。我需要创建基本的 OS window(这样我以后可以 link 它作为 DirectX 的设备或为 OpenGL 等创建上下文)。我找到了一个使用 C# 创建 Win32 window 的示例,所以我将它用作原型(示例代码在我的机器上运行正常),但是源代码就像一个单独的程序一样,我需要将它实现为class,我可以从中获取要创建的 IntPtr window。

我编写了自己的 Win32Window class,实现了所有必需的功能。然后,我使用另一个项目对其进行测试(我正在将此 class 作为我的 gameengine dll 的一部分编写),但它无法创建 window。 涉及此主题的另一个问题(在 Whosebug 和其他论坛上)有不同的问题,这些问题来自使用我不需要的 winApi 功能(如控件和其他东西)或不正确的注册顺序 window class并创建 window。

正如标题所说,我发现CreateWindowEx returns null。

Marshal.GetLastWin32Error 表示 CreateWindowEx 找不到已注册的 class。

我尝试使用变量字符串代替 ClassName,所以我不会写错名字。

我尝试使用带有 IntPtr 的重载版本的 CreateWindowEx 来注册 class。

他们都没有工作。

WinApi 的所有托管代码均来自 Pinvoke.com 和 MSDN 文档。

问题绝对不是因为没有给 RegisterClassEx 和 CreateWindowEx 一个实际的 hInstance 指针而不是 IntPtr.Zero。

这里是windowclass代码:

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

这里是测试窗口class代码:

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

测试控制台应用程序的 Main 方法只是创建一个新的 TestWindow 实例。

对于 CreateWindowEx()lpClassName 参数,您可以将 C# 中的 string 映射到 C++ 中的 LPCWSTR。他们不相等。

- 一种解法:

参考@dan04的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.

试试下面的代码:

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

更新:

注意:感谢@IInspectable 提醒。编辑代码以使用 Unicode API,例如 RegisterClassExW 和 CreateWindowExW,以确保我的回答保持一致。

但我不建议在新的 Windows 应用程序中使用 Unicode API。相反,对于所有带有文本参数的函数,应用程序通常应使用通用函数原型并定义 UNICODE 以将函数编译为 Unicode 函数。

参考:

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

-另一种解决方案:

CreateWindowEx() 的参数 lpClassName 也接受由先前调用 RegisterClass 或 RegisterClassEx 函数创建的 class 原子。 所以如果RegisterClassEx() successes you can use its return value (ATOM) as the replacement of the class name in CreateWindowExW()看看是否有效。在 C++ 中,它会像这样:

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

在C#中,基于上面的C#示例,使用UInt16替换ATOM,它会是这样的:

//...

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

我们有许多可执行文件 link 到许多不同的 DLL(我们编写的),这些 DLL 使用 C++、C# 和 C++/CLI。突然之间,我开始在我们的应用程序中收到错误 1407,该错误仅在程序在 Visual Studio 2015 调试器中 运行 时发生。

我终于通过将调试器类型选项更改为混合来解决问题 "Native Only"

出于某种原因改回 "Native Only" 不会导致错误 1407 错误再次出现。

希望这对那些问题与上述答案无关的人有所帮助