Naruszenie dostępu do odczytu przy użyciu m_pRenderTarget z winapi z powodu wskaźnika innego niż NULL

Nov 25 2020

Śledzę interfejs Microsoft Docs for Windows. Jestem obecnie w rozdziale 4 i próbuję narysować elipsę. m_pRenderTargetJest zadeklarowana w klasie App.h. W funkcji OnRender(HWND hwnd)próbuję użyć go do narysowania geometrii (elipsy). Jednak pojawia się następujący błąd:

Zgłoszono wyjątek: naruszenie prawa do odczytu. this-> m_pRenderTarget to 0x38.

Po pewnym debugowaniu zauważyłem w HRESULT App::CreateDeviceResources(HWND hwnd)funkcji, że m_pRenderTargetz jakiegoś powodu nie ma wartości NULL, mimo że zainicjowałem ją jako taką i jeszcze jej nie zmieniłem (przynajmniej nie sądzę). Domyślam się, że to jest problem. Dla odniesienia, oto odpowiedni kod:

#pragma once
#define MAX_LOADSTRING 100
#include "resource.h"
#include "pch.h"

class App
{
public:
    App();
    ~App();

    bool Init(HINSTANCE instance, int cmd);
    int RunMessageLoop();
    HINSTANCE getInstance() { return hInstance; }
private:
    HINSTANCE hInstance;
    TCHAR szTitle[MAX_LOADSTRING];
    TCHAR szWindowClass[MAX_LOADSTRING];

    ID2D1Factory* m_pD2DFactory; 
    ID2D1EllipseGeometry* m_pEllipseGeometry;
    ID2D1HwndRenderTarget* m_pRenderTarget;
    ID2D1SolidColorBrush* m_pBlackBrush;

    ATOM RegisterClass();
    BOOL InitInstance(int);
    static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    static INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
    
    HRESULT CreateDeviceIndependentResources();
    HRESULT CreateDeviceResources(HWND hwnd);
    HRESULT OnRender(HWND hwnd);
};

Oto implementacje:

#include "pch.h"
#include "App.h"


App::App()
    : m_pRenderTarget(NULL)
{}
App::~App(){}

bool App::Init(HINSTANCE instance, int cmd)
{
    hInstance = instance;
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadString(hInstance, IDC_PRACTICE, szWindowClass, MAX_LOADSTRING);

    RegisterClass();
    if (!InitInstance(cmd))
        return false;

    return true;
}

int App::RunMessageLoop()
{
    HACCEL hAccelTable;
    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PRACTICE));

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}


//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM App::RegisterClass()
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PRACTICE));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_PRACTICE);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}


BOOL App::InitInstance(int nCmdShow)
{
    HRESULT hr = CreateDeviceIndependentResources();
    if FAILED(hr) 
    {
        return FALSE;
    }

    HWND hWnd;

    hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, NULL, NULL, hInstance, this);


    if (!hWnd)
    {
        return FALSE;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//

LRESULT CALLBACK App::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    App* pApp; 
    if (message == WM_CREATE) 
    {
        LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
        pApp = (App*)pcs->lpCreateParams; 
        ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, PtrToUlong(pApp));
        return TRUE;
    }
    else
    {
        pApp = reinterpret_cast<App*>(static_cast<LONG_PTR>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)));
        if (!pApp)
            return DefWindowProc(hWnd, message, wParam, lParam); 
    }

    int wmld, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;

    switch (message)
    {
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT: 
            DialogBox(NULL, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, pApp->About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_PAINT:
    {
        hdc = BeginPaint(hWnd, &ps);
        pApp->OnRender(hWnd);
        EndPaint(hWnd, &ps);
        break;
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// Message handler for about box.
INT_PTR CALLBACK App::About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
{
    UNREFERENCED_PARAMETER(lParam); 
    switch (message) 
    {
        case WM_INITDIALOG: 
            return (INT_PTR)TRUE;

        case WM_COMMAND: 
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
            {
                EndDialog(hDlg, LOWORD(wParam));
                return (INT_PTR)TRUE; 
            } 
            break; 
    } 
        return (INT_PTR)FALSE;
}

HRESULT App::CreateDeviceIndependentResources() 
{
    HRESULT hr; 
    // Create a Direct2D factory 
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory); 
    if (SUCCEEDED(hr))
    {
        // Create an ellipse geometry
        const D2D1_ELLIPSE ellipse = D2D1::Ellipse(D2D1::Point2F(105.0f, 105.0f), 25.0f, 25.0f); 
        hr = m_pD2DFactory->CreateEllipseGeometry(ellipse, &m_pEllipseGeometry); 
    }
    return hr;
}

HRESULT App::CreateDeviceResources(HWND hwnd) 
{
    //Notice that this causes HERE to be printed out, indicating that m_pRenderTarget != NULL
    if (m_pRenderTarget != NULL)
        OutputDebugStringA("\nHERE\n");
    else
        OutputDebugStringA("\nTHERE\n");

    HRESULT hr = S_OK; 
    if (!m_pRenderTarget) {
        
        RECT rc;
        GetClientRect(hwnd, &rc); 
        D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); 
        // Create a Direct2D render target 
        hr = m_pD2DFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), &m_pRenderTarget ); 
        if (SUCCEEDED(hr)) 
        { // Create a black brush
            hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::Black), &m_pBlackBrush ); 
        }
    }

    return hr; 
}

HRESULT App::OnRender(HWND hwnd) 
{ 
    HRESULT hr;

    hr = CreateDeviceResources(hwnd); 
    if (SUCCEEDED(hr)) 
    { 
        if (!(m_pRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED)) 
        {

            m_pRenderTarget->BeginDraw(); 
            m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
            m_pRenderTarget->FillGeometry(m_pEllipseGeometry, m_pBlackBrush); 
            hr = m_pRenderTarget->EndDraw(); 
            if (hr == D2DERR_RECREATE_TARGET) 
            {
                hr = S_OK; 
                m_pRenderTarget->Release();
                m_pRenderTarget = NULL;
                m_pBlackBrush->Release(); 
                m_pBlackBrush = NULL; 
            }
        }
    }
    return hr;
}

Przepraszamy za ilość oferowanego kodu. Jestem całkowicie zdezorientowany co do problemu, ponieważ myślałem, że kopiowałem dokładnie to, co było napisane w dokumentach. Przypuszczam, że popełniłem błąd w transkrypcji. Dziękuję za wszelką pomoc, jaką możesz zaoferować.

Odpowiedzi

1 ZhuSong-MSFT Nov 25 2020 at 14:32

Powodem jest to, że podczas korzystania z CreateWindowfunkcji napisałeś dodatkowy parametr , który spowodował, hInstanceże został pobrany przez lpCreateParams, co spowodowało wyjątek dostępu.

Po prostu zmodyfikuj kod w następujący sposób:

hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), this);

I to działa dla mnie:

2 paddy Nov 25 2020 at 10:19

Ten rodzaj problemu jest jednym z następujących:

  • wskaźnik twojej klasy jest nieprawidłowy z powodu zniszczenia, niezainicjowania lub w inny sposób;
  • masz uszkodzenie stosu lub sterty, np. przepełnienie bufora lub inne niezdefiniowane zachowanie.

Teraz spójrzmy na ciągi pojawiające się w twojej klasie, które są kandydatami do przepełnienia bufora. Nie, wygląda na to, że dobrze je inicjalizujesz.

OK, bardziej statyczna analiza. Pracuj wstecz od miejsca wystąpienia problemu.

  • kto dzwonił CreateDeviceResources? To byłoOnRender
  • kto dzwonił OnRender? To był przewodnik WM_PAINT.
  • to wezwanie jest bardzo proste: pApp->OnRender(hWnd);
  • tak, jest pAppważne? Gdzie to jest zainicjowane?
  • jest przechowywany w długim wskaźniku okna - kiedy to jest przechowywane?

I to prowadzi mnie do tej linii:

::SetWindowLongPtrW(hWnd, GWLP_USERDATA, PtrToUlong(pApp));

Okay, co w tym podejrzanego? Cóż, PtrToULongnie jest to funkcja (ani makro), z której kiedykolwiek korzystałem, ale biorąc pod uwagę jej nazwę, od razu jestem podejrzliwy, ponieważ jestem przyzwyczajony do programowania w 64-bitach i wiem, że typ ULONGw Windows API to 32- kawałek.

Więc idę i sprawdzam dokumenty i na pewno stwierdzam, że tak jest. Nie tylko to, ale znalazłem kilka artykułów o tym, że jest to częsty winowajca łamania programów podczas przenoszenia na 64-bitowy.

Zauważ, że mogłeś już tu przybyć, gdybyś dołączył swój debugger, który zepsuje się, gdy nastąpi naruszenie zasad dostępu. A potem mogłeś spojrzeć na stos wywołań. Możesz wtedy sprawdzić inną zawartość swojej pAppklasy i prawdopodobnie zobaczyć, że całość jest uszkodzona.

W tym momencie po prostu mówisz „hej, wiem, że to jest szkicowe” i zmieniasz wiersz na coś takiego:

::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pApp));

Teraz skompiluj swój program i spróbuj ponownie. Założę się, że to działa. Tak bardzo, że całą odpowiedź napisałem na podstawie tego zakładu.

Innym sposobem, w jaki mógłbyś dojść do tej konkretnej ścieżki dochodzenia, byłoby wydrukowanie wartości thisw Appkonstruktorze i sprawdzenie go ponownie, OnRendergdy zobaczysz, że coś się komplikuje.