Lesezugriffsverletzung mit m_pRenderTarget mit winapi aufgrund eines Nicht-NULL-Zeigers

Nov 25 2020

Ich folge der Microsoft Docs für Windows-API. Ich bin gerade in Kapitel 4 und versuche eine Ellipse zu zeichnen. Das m_pRenderTargetwird in der Klasse App.h deklariert. In der Funktion OnRender(HWND hwnd)versuche ich damit die Geometrie (Ellipse) zu zeichnen. Ich erhalte jedoch den folgenden Fehler:

Ausnahme ausgelöst: Lesezugriffsverletzung. this-> m_pRenderTarget war 0x38.

Nach einigem Debuggen bemerkte ich, dass die HRESULT App::CreateDeviceResources(HWND hwnd)Funktion m_pRenderTargetaus irgendeinem Grund nicht NULL war, obwohl ich sie als solche initialisiert und noch nicht geändert hatte (glaube ich zumindest nicht). Ich vermute, dass dies das Problem ist. Als Referenz ist hier der relevante Code:

#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);
};

Hier sind die Implementierungen:

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

Ich entschuldige mich für die Menge an Code, die ich anbiete. Ich bin völlig verwirrt über das Problem, da ich gedacht hatte, dass ich genau das kopiere, was in den Dokumenten geschrieben steht. Ich muss wohl einen Transkriptionsfehler gemacht haben. Vielen Dank für jede Hilfe, die Sie anbieten können.

Antworten

1 ZhuSong-MSFT Nov 25 2020 at 14:32

Der Grund dafür ist , dass Sie einen zusätzlichen Parameter geschrieben , wenn die Verwendung von CreateWindowFunktion, die verursacht hInstancedurch erworben werden lpCreateParams, die eine Zugriffs Ausnahme verursacht hat .

Ändern Sie einfach den Code wie folgt:

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

Und es funktioniert bei mir:

2 paddy Nov 25 2020 at 10:19

Diese Art von Problem schreit eines der folgenden:

  • Ihr Klassenzeiger ist ungültig, weil er zerstört, nicht initialisiert oder auf andere Weise erstellt wurde.
  • Sie haben eine Stapel- oder Heap-Beschädigung, z. B. einen Pufferüberlauf oder ein anderes undefiniertes Verhalten.

Schauen wir uns nun die Zeichenfolgen in Ihrer Klasse an, die Kandidaten für einen Pufferüberlauf sind. Nein, es sieht so aus, als würden Sie diese fein initialisieren.

Okay, mehr statische Analyse. Arbeiten Sie rückwärts, von wo aus das Problem auftritt.

  • Wer hat angerufen CreateDeviceResources? Es warOnRender
  • Wer hat angerufen OnRender? Es war der WM_PAINTHandler.
  • Dieser Aufruf ist sehr einfach: pApp->OnRender(hWnd);
  • Also, ist pAppgültig? Wo ist das initialisiert?
  • es ist im fensterlangen Zeiger gespeichert - wann ist das gespeichert?

Und das führt mich zu dieser Zeile:

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

Okay, was ist daran faul? Nun, es PtrToULongist keine Funktion (oder kein Makro), die ich jemals verwendet habe, aber aufgrund ihres Namens bin ich sofort misstrauisch, weil ich es gewohnt bin, in 64-Bit zu programmieren, und ich weiß, dass der Typ ULONGin der Windows-API 32- ist. bisschen.

Also gehe ich in die Dokumentation und finde, dass dies der Fall ist. Nicht nur das, sondern ich finde auch mehrere Schriften darüber, wie dies tatsächlich ein häufiger Schuldiger für das Unterbrechen von Programmen beim Portieren auf 64-Bit ist.

Beachten Sie, dass Sie möglicherweise bereits hier angekommen sind, wenn Sie Ihren Debugger angehängt haben. Dieser würde bei Auftreten der Zugriffsverletzung unterbrochen. Und dann hätten Sie sich den Aufrufstapel ansehen können. Sie könnten dann den anderen Inhalt Ihrer pAppKlasse überprüfen und wahrscheinlich feststellen, dass das Ganze beschädigt ist.

An diesem Punkt sagen Sie einfach "Hey, ich weiß, das ist lückenhaft" und ändern die Zeile in etwas wie:

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

Jetzt kompilieren Sie Ihr Programm und versuchen es erneut. Ich bin bereit zu wetten, dass es funktioniert. So sehr, dass ich diese ganze Antwort basierend auf dieser Wette geschrieben habe.

Eine andere Möglichkeit, zu diesem bestimmten Untersuchungspfad zu gelangen, wäre gewesen, den Wert von thisin Ihrem AppKonstruktor auszudrucken und ihn erneut einzuchecken, OnRenderwenn Sie sahen, dass die Dinge wackelig wurden.