следуйте официальному образцу Direct2D, но получили ошибку нарушения прав доступа

Следуя официальному руководству по Direct2D (https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-quickstart), чтобы создать пример проекта с Visual Studio 2019. При запуске кода в x86 все работает нормально, при смене платформы на x64 я получаю ошибка, которая гласит: «Вызвано исключение: нарушение доступа для чтения». в SampleD2D.cpp. (строка была закомментирована в коде ниже)

ошибка:

Exception thrown: read access violation.
this was 0xBB18F6E8.
    LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT result = 0;

    if (message == WM_CREATE)
    {
        LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
        DemoApp* pDemoApp = (DemoApp*)pcs->lpCreateParams;

        ::SetWindowLongPtrW(
            hwnd,
            GWLP_USERDATA,
            PtrToUlong(pDemoApp)
        );

        result = 1;
    }
    else
    {
        DemoApp* pDemoApp = reinterpret_cast<DemoApp*>(static_cast<LONG_PTR>(
            ::GetWindowLongPtrW(
                hwnd,
                GWLP_USERDATA
            )));

        bool wasHandled = false;

        if (pDemoApp)
        {
            switch (message)
            {
            case WM_SIZE:
            {
                UINT width = LOWORD(lParam);
                UINT height = HIWORD(lParam);
                pDemoApp->OnResize(width, height); // throw the error!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            }
            result = 0;
            wasHandled = true;
            break;

            case WM_DISPLAYCHANGE:
            {
                InvalidateRect(hwnd, NULL, FALSE);
            }
            result = 0;
            wasHandled = true;
            break;

            case WM_PAINT:
            {
                pDemoApp->OnRender();
                ValidateRect(hwnd, NULL);
            }
            result = 0;
            wasHandled = true;
            break;

            case WM_DESTROY:
            {
                PostQuitMessage(0);
            }
            result = 1;
            wasHandled = true;
            break;
            }
        }

        if (!wasHandled)
        {
            result = DefWindowProc(hwnd, message, wParam, lParam);
        }
    }

    return result;
}

person shukehongqiao    schedule 16.08.2019    source источник
comment
Вы изменили что-нибудь еще? Каковы характеристики вашей системы?   -  person Thomas    schedule 16.08.2019
comment
Эй, я ничего не менял. Это Windows 10 Pro с процессором i7-8700.   -  person shukehongqiao    schedule 16.08.2019
comment
Кстати. Я просмотрел вашу ссылку и нашел неправильный код в части 4 (как вы утверждаете). Вы можете сделать образец отчета об ошибке (и передать привет от меня). ;-)   -  person Scheff's Cat    schedule 16.08.2019
comment
У MS есть ошибка в образце, и она не хочет ее исправлять. Вот вариант получше: github.com/Const-me/Direct2D-demo   -  person Soonts    schedule 16.08.2019
comment
@ Вскоре я не ожидал такой точной копии. (Интересно, почему я не нашел этого при гуглении...) С другой стороны, ОП вряд ли был первым, кто попал в эту ловушку. Жалко (для MS). ;-) (Или действительно полезно обострить свой глаз? Маленькие ошибки здесь и там заставляют вас (действительно) понять, что происходит...)   -  person Scheff's Cat    schedule 17.08.2019


Ответы (1)


К сожалению, я не специалист по WinAPI, но из любопытства немного погуглил. Теперь я совершенно уверен в проблеме OP:

        ::SetWindowLongPtrW(
            hwnd,
            GWLP_USERDATA,
            PtrToUlong(pDemoApp)
        );

конкретно PtrToUlong(pDemoApp).

Это может работать для 32-битных приложений, но не для 64-битных.

long находится в 32-разрядной версии MS VC++ для платформы x86, а также для платформы x64.

Следовательно, преобразование указателя в long или unsigned long полезно для того, чтобы сделать его неправильным на x64 (поскольку старшие 32 бита не равны 0, что, вероятно, трудно предсказать).

Погуглив в этом направлении, я нашел, например. PtrToUlong Q/A на gamedev.net с этим (старым) ответом:

msdn, старайтесь не использовать их, потому что вы приводите указатель в unsigned long. Это может работать правильно на 32-битных исполняемых файлах, но если вы скомпилируете в 64-битных, у вас могут возникнуть проблемы.

что подтверждает мои сомнения.

Согласно МС док. функция SetWindowLongPtrW, подпись :

LONG_PTR SetWindowLongPtrW(
  HWND     hWnd,
  int      nIndex,
  LONG_PTR dwNewLong
);

Итак, это должно исправить это:

        ::SetWindowLongPtrW(
            hwnd,
            GWLP_USERDATA,
            reinterpret_cast<LONG_PTR>(pDemoApp)
        );

Пожалуйста, обратите внимание на документ MS. о LONG_PTR:

LONG_PTR

Длинный тип со знаком для точности указателя. Используйте при приведении указателя к типу long для выполнения арифметических действий с указателем.

Этот тип объявлен в BaseTsd.h следующим образом:

С++

#if defined(_WIN64)
 typedef __int64 LONG_PTR; 
#else
 typedef long LONG_PTR;
#endif

Кстати. я тоже не поняла

        DemoApp* pDemoApp = reinterpret_cast<DemoApp*>(static_cast<LONG_PTR>(
            ::GetWindowLongPtrW(
                hwnd,
                GWLP_USERDATA
            )));

Согласно док. GetWindowLongPtrW, функция возвращает LONG_PTR. Итак, почему static_cast<LONG_PTR>? Приведение типа всегда должно быть последним средством, если это абсолютно необходимо. (Хотя я признаю, что без WinAPI, вероятно, невозможно использовать.)

person Scheff's Cat    schedule 16.08.2019