Lea la infracción de acceso usando m_pRenderTarget con winapi debido a un puntero no NULL

Nov 25 2020

Estoy siguiendo la API de Microsoft Docs para Windows. Actualmente estoy en el capítulo 4 y trato de dibujar una elipse. El m_pRenderTargetestá declarado en la clase App.h. En la función OnRender(HWND hwnd), estoy tratando de usarla para dibujar la geometría (elipse). Sin embargo, recibo el siguiente error:

Excepción lanzada: violación de acceso de lectura. this-> m_pRenderTarget era 0x38.

Después de un poco de depuración, noté que en la HRESULT App::CreateDeviceResources(HWND hwnd)función, m_pRenderTargetpor alguna razón, no era NULL, aunque la inicialicé como tal y aún no la había cambiado (no creo que al menos). Supongo que este es el problema. Como referencia, aquí está el código relevante:

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

Aquí están las implementaciones:

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

Pido disculpas por la cantidad de código que ofrezco. Estoy completamente confundido sobre el problema, ya que pensé que estaba copiando exactamente lo que estaba escrito en los documentos. Supongo que debo haber cometido un error de transcripción. Gracias por cualquier ayuda que pueda ofrecer.

Respuestas

1 ZhuSong-MSFT Nov 25 2020 at 14:32

La razón es que escribió un parámetro adicional al usar la CreateWindowfunción, lo que provocó hInstanceque lo adquiriera lpCreateParams, lo que provocó una excepción de acceso.

Simplemente modifique el código de la siguiente manera:

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

Y funciona para mi:

2 paddy Nov 25 2020 at 10:19

Este tipo de problema grita uno de los siguientes:

  • el puntero de su clase no es válido por haber sido destruido, no inicializado o de otra manera;
  • tiene corrupción de pila o montón, por ejemplo, desbordamiento de búfer u otro comportamiento indefinido.

Ahora, veamos las cadenas que aparecen en su clase, que son candidatas para el desbordamiento del búfer. No, parece que los está inicializando bien.

Bien, análisis más estático. Trabaje al revés desde donde ocurre el problema.

  • quien llama CreateDeviceResources? EraOnRender
  • quien llama OnRender? Fue el WM_PAINTmanejador.
  • esa llamada es muy simple: pApp->OnRender(hWnd);
  • entonces, ¿es pAppválido? ¿Dónde está eso inicializado?
  • se almacena en el puntero largo de la ventana: ¿cuándo se almacena?

Y esto me lleva a esta línea:

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

Bien, ¿qué tiene de sospechoso eso? Bueno, PtrToULongno es una función (o macro) que haya usado alguna vez, pero según su nombre, sospecho de inmediato porque estoy acostumbrado a programar en 64 bits y sé que el tipo ULONGen la API de Windows es 32- poco.

Entonces, voy y reviso los documentos, y efectivamente encuentro que ese es el caso. No solo esto, sino que encuentro varios escritos sobre cómo esto es en realidad un culpable común de romper programas cuando se transfieren a 64 bits.

Tenga en cuenta que ya podría haber llegado aquí si hubiera adjuntado su depurador, que se rompería cuando se produzca la infracción de acceso. Y luego podrías haber mirado la pila de llamadas. Luego, podría verificar el resto de contenidos de su pAppclase y probablemente ver que todo está dañado.

En este punto, solo tienes que decir "oye, sé que esto es incompleto" y cambias la línea a algo como:

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

Ahora compila su programa y vuelve a intentarlo. Estoy dispuesto a apostar que funciona. Tanto es así, que he escrito toda esta respuesta basada en esa apuesta.

Otra forma en que podría haber llegado a este camino particular de investigación habría sido imprimir el valor de thisen su Appconstructor y verificarlo nuevamente OnRendercuando vea que las cosas van mal.