Violação de acesso de leitura usando m_pRenderTarget com winapi devido ao ponteiro não NULL
Estou seguindo a API do Microsoft Docs para Windows. Estou atualmente no capítulo 4 e estou tentando desenhar uma elipse. O m_pRenderTarget
é declarado na classe App.h. Na função OnRender(HWND hwnd)
estou tentando usá-lo para desenhar a geometria (elipse). No entanto, estou recebendo o seguinte erro:
Exceção lançada: violação de acesso de leitura. this-> m_pRenderTarget era 0x38.
Depois de alguma depuração, notei que na HRESULT App::CreateDeviceResources(HWND hwnd)
função, m_pRenderTarget
por algum motivo, não era NULL, embora eu tenha inicializado como tal e ainda não tenha alterado (acho que pelo menos não). Meu palpite é que esse é o problema. Para referência, aqui está o 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);
};
Aqui estão as implementações:
#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;
}
Peço desculpas pela quantidade de código que estou oferecendo. Estou completamente confuso sobre o problema, pois pensei que estava copiando exatamente o que estava escrito nos documentos. Suponho que devo ter cometido um erro de transcrição. Obrigado por qualquer ajuda que você possa oferecer.
Respostas
O motivo é que você escreveu um parâmetro extra ao usar a CreateWindow
função, que causou hInstance
a aquisição por lpCreateParams
, o que causou uma exceção de acesso.
Basta modificar o código da seguinte maneira:
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), this);
E funciona para mim:

Esse tipo de problema grita um dos seguintes:
- seu ponteiro de classe é inválido por ser destruído, não inicializado ou de outra forma;
- você tem pilha ou corrupção de heap, por exemplo, estouro de buffer ou outro comportamento indefinido.
Agora, vamos dar uma olhada nas strings que aparecem em sua classe, que são candidatas a estouro de buffer. Não, parece que você está inicializando corretamente.
Ok, mais análise estática. Trabalhe de onde o problema ocorre.
- quem ligou
CreateDeviceResources
? isso foiOnRender
- quem ligou
OnRender
? Era oWM_PAINT
manipulador. - essa chamada é muito simples:
pApp->OnRender(hWnd);
- então, é
pApp
válido? Onde isso é inicializado? - ele é armazenado no ponteiro longo da janela - quando é armazenado?
E isso me leva a esta linha:
::SetWindowLongPtrW(hWnd, GWLP_USERDATA, PtrToUlong(pApp));
Ok, o que há de suspeito nisso? Bem, PtrToULong
não é uma função (ou macro) que eu já usei, mas com base em seu nome, fico imediatamente desconfiado porque estou acostumado a programar em 64 bits e sei que o tipo ULONG
na API do Windows é 32- mordeu.
Então, vou verificar os documentos e, com certeza, acho que é esse o caso. Não apenas isso, mas encontro vários escritos sobre como isso é realmente um culpado comum por quebrar programas ao portar para 64 bits.
Observe que você já poderia ter chegado aqui se tivesse anexado seu depurador, que seria interrompido quando ocorresse a violação de acesso. E então você poderia ter olhado a pilha de chamadas. Você pode então verificar o outro conteúdo de sua pApp
classe e provavelmente ver que tudo está corrompido.
Nesse ponto, você apenas diz "ei, eu sei que isso é incompleto" e altera a linha para algo como:
::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pApp));
Agora você compila seu programa e tenta novamente. Estou disposto a apostar que funciona. Tanto é verdade que escrevi toda esta resposta com base nessa aposta.
Outra maneira pela qual você poderia ter chegado a esse caminho específico de investigação seria imprimir o valor de this
em seu App
construtor e verificá-lo novamente OnRender
quando perceber que as coisas estavam complicadas.