【问题标题】:How to fix windows error 1407 (Cannot find window class) when trying to implement WinApi in custom c# window class?尝试在自定义 c# 窗口类中实现 WinApi 时如何修复 Windows 错误 1407(找不到窗口类)?
【发布时间】:2019-04-29 20:24:24
【问题描述】:

我目前正在编写自定义游戏引擎(用于自学)并研究窗口系统。我需要创建基本的操作系统窗口(这样我以后可以将其链接为 DirectX 的设备或为 OpenGL 创建上下文等)。我找到了一个使用 C# 创建 Win32 窗口的示例,因此我将其用作原型(示例代码在我的机器上正常工作),但是源代码就像一个单独的程序一样,我需要将它作为一个类来实现,从中我可以获得一个 IntPtr 来创建窗口。

我编写了自己的 Win32Window 类,实现了所有必需的功能。然后,我使用另一个项目对其进行了测试(我正在编写这个类作为我的游戏引擎 dll 的一部分),但它无法创建窗口。 涉及该主题的另一个问题(在 Stackoverflow 和其他论坛上)有不同的问题,这些问题来自使用我不需要的 winApi 功能(如控件和其他东西)或注册窗口类和创建窗口的错误顺序。

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

Marshal.GetLastWin32Error 说 CreateWindowEx 找不到注册的类。

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

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

他们都没有工作。

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

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

这是窗口类代码:

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

这是TestWindow类代码:

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 实例。

【问题讨论】:

  • 错误信息似乎很清楚。尝试调试。检查您创建时使用的类名称是否与您注册时使用的名称匹配。

标签: c# winapi window


【解决方案1】:

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

- 一种解决方案:

参考@dan04的answer

C# 使用 UTF-16 字符串,因此您会希望使用“W”版本的 这些功能。使用 PdhOpenQueryW。那么第一个参数有C++ 输入 const wchar_t*。 C# 类型是 [MarshalAs(UnmanagedType.LPWStr)] 字符串。

试试下面的代码:

    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 的提醒。编辑代码以使用像 RegisterClassExW 和 CreateWindowExW 这样的 Unicode API,以使我的答案保持一致。

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

参考:

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

- 另一种解决方案:

CreateWindowEx() 的参数lpClassName 也接受由先前调用RegisterClass 或RegisterClassEx 函数创建的类原子。 因此,如果RegisterClassEx() 成功,您可以使用它的return value (ATOM) 作为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);

【讨论】:

  • 我认为没有任何方法可以诊断失败的原因是错误的文本编码。
  • 这是引入了很多字符编码不匹配的问题,这些问题后来被修复,并带来了新的失败机会。除非您绝对需要您的代码在 Windows 95/98/ME 上运行,否则建议很简单:使用CharSet.Unicode everywhere,并导入 Windows API 的 Unicode 版本(通常带有尾随 @987654339 @ 在函数名中)。
  • 1.真正的问题是我应该从模块内部获取 hInstance 指针(就像在您的示例中一样;这可能是因为不是应用程序,而是 dll 启动窗口) 2. 如果您编组并不重要WNDCLASS 结构中的字符串 - 您应该确保在 CreateWindowEx() 声明中执行此操作(刚刚测试过,以防其他人将来需要它)
  • “我不建议在新的 Windows 应用程序中使用 Unicode API。” - 这很奇怪。在编写 C# 代码时,您已经在使用 UTF-16LE。这就是String 在内部使用的内容,您甚至无法选择更改它。同样,Windows API 几乎完全作为 Unicode 接口公开,而 ANSI 导出是包装器。通过允许您的代码的客户灵活地选择他们不想要的选项,您正在尝试解决什么问题,目前尚不清楚。
  • 好像有什么误会。首先,我的意思是建议在代码中使用诸如 RegisterClassEx (A generic version that can be compiled for either Windows code pages or Unicode.) 之类的 API 而不是 RegisterClassExW,选择哪个函数原型由编译条件决定。一些较新的函数仅支持 Unicode 版本,例如 LCIDToLocaleName 和 LocaleNameToLCID,没有字母“W”后缀。其次,我说的是 C++ 原生 API 而不是 C# API。
【解决方案2】:

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

在将调试器类型选项更改为“仅本机”后,我终于设法解决了这个问题

由于某种原因,改回“仅限本机”不会导致错误 1407 错误再次出现。

希望这可以帮助任何问题与上述答案无关的人

【讨论】:

    猜你喜欢
    • 2010-10-18
    • 2013-02-25
    • 1970-01-01
    • 2021-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-20
    相关资源
    最近更新 更多