Violation d'accès en lecture à l'aide de m_pRenderTarget avec winapi en raison d'un pointeur non NULL

Nov 25 2020

Je suis l'API Microsoft Docs pour Windows. Je suis actuellement au chapitre 4 et j'essaye de dessiner une ellipse. Le m_pRenderTargetest déclaré dans la classe App.h. Dans la fonction OnRender(HWND hwnd), j'essaie de l'utiliser pour dessiner la géométrie (ellipse). Cependant, j'obtiens l'erreur suivante:

Exception levée: violation d'accès en lecture. this-> m_pRenderTarget était 0x38.

Après quelques débogages, j'ai remarqué que la HRESULT App::CreateDeviceResources(HWND hwnd)fonction dans la fonction m_pRenderTargetn'était pas NULL pour une raison quelconque, même si je l'ai initialisée en tant que telle et ne l'avais pas encore modifiée (je ne pense pas du moins). Je suppose que c'est là le problème. Pour référence, voici le code correspondant:

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

Voici les implémentations:

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

Je m'excuse pour la quantité de code que j'offre. Je suis complètement confus sur le problème car j'avais pensé que je copiais exactement ce qui était écrit dans la documentation. Je suppose que j'ai dû faire une erreur de transcription. Merci pour toute aide que vous pouvez offrir.

Réponses

1 ZhuSong-MSFT Nov 25 2020 at 14:32

La raison en est que vous avez écrit un paramètre supplémentaire lors de l'utilisation de la CreateWindowfonction, ce qui a provoqué hInstancel'acquisition de lpCreateParams, ce qui a provoqué une exception d'accès.

Modifiez simplement le code comme suit:

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

Et cela fonctionne pour moi:

2 paddy Nov 25 2020 at 10:19

Ce genre de problème crie l'un des suivants:

  • votre pointeur de classe est invalide car détruit, non initialisé ou autrement;
  • vous avez une corruption de pile ou de tas, par exemple un débordement de tampon ou un autre comportement non défini.

Maintenant, regardons les chaînes apparaissant dans votre classe, qui sont des candidats au débordement de tampon. Non, il semble que vous les initialisez correctement.

D'accord, plus d'analyse statique. Travaillez en arrière à partir de l'endroit où le problème se produit.

  • qui a appelé CreateDeviceResources? C'étaitOnRender
  • qui a appelé OnRender? C'était le WM_PAINTgestionnaire.
  • cet appel est très simple: pApp->OnRender(hWnd);
  • oui, est-ce pAppvalable? Où est-ce initialisé?
  • il est stocké dans le pointeur long de la fenêtre - quand est-ce stocké?

Et cela m'amène à cette ligne:

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

D'accord, qu'est-ce qui est louche à ce sujet? Eh bien, ce PtrToULongn'est pas une fonction (ou une macro) que j'ai jamais utilisée, mais en fonction de son nom, je me méfie immédiatement car je suis habitué à programmer en 64 bits et je sais que le type ULONGdans l'API Windows est 32- bit.

Alors, je vais vérifier les documents, et bien sûr je trouve que c'est le cas. Non seulement cela, mais je trouve plusieurs écrits sur la façon dont c'est en fait un coupable courant pour la rupture des programmes lors du portage en 64 bits.

Notez que vous pourriez déjà être arrivé ici si vous aviez attaché votre débogueur, qui se briserait lorsque la violation d'accès se produirait. Et puis vous auriez pu regarder la pile d'appels. Vous pouvez alors vérifier les autres contenus de votre pAppclasse et voir probablement que tout est corrompu.

À ce stade, vous dites simplement "hé, je sais que c'est sommaire", et vous changez la ligne en quelque chose comme:

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

Maintenant, compilez votre programme et réessayez. Je suis prêt à parier que cela fonctionne. À tel point que j'ai écrit toute cette réponse basée sur ce pari.

Une autre façon dont vous auriez pu arriver à ce chemin particulier d'enquête aurait été d'imprimer la valeur de thisdans votre Appconstructeur et de la réenregistrer OnRenderlorsque vous avez vu les choses aller de travers.