尝试在自定义C#窗口类中实现WinApi时,如何解决Windows错误1407(找不到窗口类)?

时间:2019-04-29 20:24:24

标签: c# winapi window

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

我编写了自己的Win32Window类,实现了所有必需的功能。然后,我使用另一个项目对其进行了测试(我正在将此类作为我的gameengine dll的一部分编写),但是它无法创建窗口。 另一个与该主题相关的问题(在Stackoverflow和其他论坛上)有不同的问题,这些问题来自使用我不需要的winApi功能(例如控件和其他内容),或者注册窗口类和创建窗口的顺序不正确。

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

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

我试图在ClassName位置使用变量字符串,所以我不会错印名称。

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

他们都不起作用。

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

绝对不是由于没有将实际的hInstance指针提供给RegisterClassEx和CreateWindowEx而不是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();
        }
    }

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

2 个答案:

答案 0 :(得分:0)

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

在将调试器类型选项更改为“仅限本机”之后,我终于设法通过将“调试器类型”选项更改为“混合”来解决了该问题。

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

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

答案 1 :(得分:-1)

对于string的{​​{1}}参数,您可以将C#中的LPCWSTR映射到C ++中的lpClassName。他们不平等。

-一种解决方案:

请参阅@ dan04的answer

  

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

尝试以下代码:

CreateWindowEx()

更新

注意:感谢@IInspectable的提醒。编辑代码以使用Unicode API(例如RegisterClassExW和CreateWindowExW)来确保答案的一致性。

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

参考:

Unicode in the Windows APIConventions for Function PrototypesDefault Marshaling for Strings

-另一种解决方案:

CreateWindowEx()的参数 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); } } 也接受通过先前调用RegisterClass或RegisterClassEx函数创建的类原子。 因此,如果RegisterClassEx()成功,则可以使用其return value (ATOM)作为CreateWindowExW()中类名的替换,以查看其是否有效。在C ++中,它将是这样的:

lpClassName

在C#中,基于上述C#示例,使用UInt16替换ATOM,它将如下:

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