NULL이 아닌 포인터로 인해 winapi와 함께 m_pRenderTarget을 사용하는 읽기 액세스 위반

Nov 25 2020

Windows API 용 Microsoft Docs를 따르고 있습니다. 저는 현재 4 장에 있으며 타원을 그리려고합니다. 는 m_pRenderTarget클래스 App.h.에 선언 함수에서 OnRender(HWND hwnd)나는 그것을 사용하여 기하학 (타원)을 그리려고합니다. 그러나 다음과 같은 오류가 발생합니다.

예외 발생 : 읽기 액세스 위반. this-> m_pRenderTarget 은 0x38입니다.

약간의 디버깅 후, 나는 그것을 초기화하고 아직 변경하지 않았지만 (적어도 생각하지 않는다) HRESULT App::CreateDeviceResources(HWND hwnd)함수에서 m_pRenderTarget어떤 이유로 NULL이 아니라는 것을 알았습니다. 내 생각 엔 이것이 문제라는 것입니다. 참고로 관련 코드는 다음과 같습니다.

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

다음은 구현입니다.

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

제공하는 코드의 양에 대해 사과드립니다. 나는 문서에 쓰여진 것을 정확하게 복사한다고 생각했기 때문에 문제에 대해 완전히 혼란 스럽습니다. 필사에 오류를 범했을 것 같다. 제공 할 수있는 도움에 감사드립니다.

답변

1 ZhuSong-MSFT Nov 25 2020 at 14:32

그 이유는 CreateWindow함수를 사용할 때 추가 매개 변수를 작성하여에 hInstance의해 획득되어 lpCreateParams액세스 예외가 발생 했기 때문입니다 .

다음과 같이 코드를 수정하십시오.

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

그리고 그것은 나를 위해 작동합니다.

2 paddy Nov 25 2020 at 10:19

이러한 종류의 문제는 다음 중 하나를 나타냅니다.

  • 클래스 포인터가 파괴되거나 초기화되지 않거나 다른 방식으로 유효하지 않습니다.
  • 스택 또는 힙 손상 ( 예 : 버퍼 오버플로 또는 기타 정의되지 않은 동작)이 있습니다.

이제 버퍼 오버플로의 후보 인 클래스에 나타나는 문자열을 살펴 보겠습니다. 아니요, 잘 초기화하고있는 것 같습니다.

좋아, 더 정적 인 분석. 문제가 발생한 곳에서 거꾸로 작업하십시오.

  • 누가 전화 했어 CreateDeviceResources? 그것은OnRender
  • 누가 전화 했어 OnRender? 그것은이었다 WM_PAINT핸들러입니다.
  • 그 호출은 매우 간단합니다. pApp->OnRender(hWnd);
  • 그래서 pApp유효합니까? 어디에서 초기화 되었습니까?
  • 창 긴 포인터에 저장됩니다. 언제 저장됩니까?

그리고 이것은 나를 다음 줄로 인도합니다.

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

좋아요, 뭐가 비린내 죠? 글쎄요, PtrToULong제가 사용 해본 함수 (또는 매크로)는 아니지만 그 이름에 따르면 64 비트로 프로그래밍하는 데 익숙하고 ULONGWindows API 의 유형 이 32 라는 것을 알고 있기 때문에 즉시 의심됩니다. 비트.

그래서 나는 가서 문서를 확인하고 그것이 사실임을 충분히 확신합니다. 뿐만 아니라 64 비트로 이식 할 때 이것이 실제로 프로그램을 중단시키는 일반적인 원인에 대한 여러 글을 발견했습니다.

디버거를 연결 한 경우 이미 여기에 도착했을 수 있으며 액세스 위반이 발생하면 중단됩니다. 그런 다음 호출 스택을 볼 수 있습니다. 그런 다음 pApp클래스 의 다른 내용을 확인하고 전체 내용이 손상되었음을 확인할 수 있습니다.

이 시점에서 "hey I know this is sketchy"로 이동하고 선을 다음과 같이 변경합니다.

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

이제 프로그램을 컴파일하고 다시 시도하십시오. 나는 그것이 효과가 있다고 확신합니다. 너무 많이, 그 내기를 ​​기반으로 전체 답변을 작성했습니다.

이 특정 조사 경로에 도달 할 수있는 또 다른 방법 thisApp생성자 에서 의 값을 인쇄하고 문제 가 OnRender발생하면 다시 확인하는 것입니다.