Как исправить ошибку Windows 1407 (не удается найти класс окна) при попытке реализовать WinApi в пользовательском классе окна c #? - PullRequest
1 голос
/ 29 апреля 2019

В настоящее время я пишу собственный игровой движок (для самообразования) и работаю над оконной системой.Мне нужно создать базовое окно ОС (чтобы потом я мог связать его как устройство для DirectX или создать контекст для OpenGL и т. Д.).Я нашел пример создания окна Win32 с C #, поэтому я использовал его в качестве прототипа (пример кода работает правильно на моей машине), но исходный код был сделан как отдельная программа, и мне нужно реализовать его как класс, из которогоя могу получить IntPtr для созданного окна.

Я написал свой собственный класс Win32Window, реализующий все необходимые функции.Затем я использовал другой проект, чтобы проверить его (я пишу этот класс как часть моего gameengine dll), но он не может создать окно.Другие вопросы (на Stackoverflow и других форумах), которые касаются этой темы, имеют различные проблемы, связанные с использованием ненужных мне функций winApi (например, элементов управления и прочего) или неправильным порядком регистрации класса окна и создания окна.

Как видно из заголовка, я обнаружил, что CreateWindowEx возвращает ноль.

Marshal.GetLastWin32Error говорит, что CreateWindowEx не может найти зарегистрированный класс.

Я попытался использовать переменную строку вместо ClassName, поэтому я не опечатался в имени.

Я пыталсяиспользовать перегруженную версию CreateWindowEx с IntPtr для зарегистрированного класса.

Ни один из них не работал.

Весь управляемый код для 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.

1 Ответ

0 голосов
/ 30 апреля 2019

Вы можете сопоставить string в C # с LPCWSTR в C ++ для lpClassName параметра CreateWindowEx(). Они не равны.

- Одно решение:

См. @ Dan04's 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 за напоминание. Измените код, чтобы использовать Unicode API, например RegisterClassExW и CreateWindowExW, для согласованности в моем ответе.

Но я не предлагаю использовать Unicode API в новом приложении Windows. Вместо этого для всех функций с текстовыми аргументами приложения должны обычно использовать прототипы обобщенных функций и определять UNICODE для компиляции функций в функции Unicode.

См .:

Юникод в Windows API , Условные обозначения для функциональных прототипов , Маршалинг по умолчанию для строк

- Другое решение:

Параметр lpClassName метода CreateWindowEx () также принимает атом класса, созданный предыдущим вызовом функции RegisterClass или RegisterClassEx. Поэтому, если RegisterClassEx () успешно, вы можете использовать его возвращаемое значение (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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...