창 투명 오버레이가 몇 초 후에 작동을 멈 춥니 다.

Dec 04 2020

일부 프로세스 창에 투명한 오버레이를 만들려고합니다 (Direct3D 9를 사용하여 오버레이에 일부 항목을 그리고 싶습니다).

오버레이는 외부 프로그램 (.dll 삽입 라이브러리 등이 아님)을 사용하여 생성됩니다.

문제는 내가 프로그램을 시작할 때 보이지 않는 오버레이가 프로세스 창 위에 나타나고 (그리고 심지어 그 위에 텍스트를 그려서 WM_PAINT가 작동하는 것처럼 보입니다) 다음 몇 초 안에 커서가 "모래 시계"가됩니다. 스타일 (죄송합니다. xd라고 부르는 다른 방법을 모르겠습니다). 창을 클릭하면 "앱이 응답하지 않습니다"라는 오류가 표시되고 흰색으로 바뀝니다.

오버레이 클래스를 진입 점 파일로 가져오고 기본 함수에서 실행하는 방법은 다음과 같습니다 (단순화 됨).

#include <iostream>
#include "memory.hpp"
#include "overlay.hpp"

int main() {
    Memory mem;
    Overlay ol(&mem);
    HANDLE overlay = CreateThread(NULL, NULL, &ol.static_start, (void*)&ol, NULL, NULL);

    while (1) {
        SendMessage(ol.hwnd, WM_PAINT, NULL, NULL);

        Sleep(2);
    }

    return EXIT_SUCCESS;
}

overlay.hpp :

#ifndef OVERLAY_HPP
#define OVERLAY_HPP
#pragma once
 
#include <Windows.h>
#include <d3d9.h>
#include "paint.hpp" // has a class with methods to draw on overlay using d3dx9
#include "memory.hpp" // has a tHWND variable - handle to target window 
#include <iostream>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")
 
Paint paint;
 
class Overlay {
private:
    WCHAR _title[20] = L"anoverlay"; // overlay window title
    // HINSTANCE hwnd_inst;
    Memory* mem;
    RECT rect; // coordinates of target window
 
    // registers window class
    ATOM _register_сlass() {
        WNDCLASSEX wc;
        ZeroMemory(&wc, sizeof(WNDCLASSEX));
 
        wc.cbSize = sizeof(WNDCLASSEX);
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = WndProc;
        wc.cbClsExtra = NULL;
        wc.cbWndExtra = NULL;
        wc.hInstance = reinterpret_cast<HINSTANCE>(GetWindowLongW(mem->tHWND, GWL_HINSTANCE)); // hwnd_inst;
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wc.hbrBackground = WHITE_BRUSH;
        wc.lpszMenuName = NULL;
        wc.lpszClassName = _title;
        wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);;
 
        return RegisterClassEx(&wc);
    }
 
    // initialise overlay instance
    bool _init_instance(int width, int height) {
        hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_LAYERED, _title, _title, WS_POPUP, rect.left, rect.top, width, height, NULL, NULL, NULL, NULL);
        if (!hwnd) return false;
 
        SetLayeredWindowAttributes(hwnd, 0, 1.0f, LWA_ALPHA);
        SetLayeredWindowAttributes(hwnd, 0, RGB(0, 0, 0), LWA_COLORKEY);
 
        MARGINS _margins = { -1, -1, -1, -1 };
        DwmExtendFrameIntoClientArea(hwnd, &_margins);
 
        ShowWindow(hwnd, SW_SHOW);
        return true;
    }
 
public:
    HWND hwnd; // an HWND to the overlay window
 
    Overlay(Memory* mem) {
        this->mem = mem;
        if (!init()) std::cout << "The overlay window was not created" << std::endl;
    }
 
    ~Overlay() {
        DestroyWindow(hwnd);
    }
 
    bool init() {
        if (!mem->tHWND) return false;
        if (!GetWindowRect(mem->tHWND, &rect)) return false;
        _register_сlass();
 
        if (!_init_instance(rect.right - rect.left, rect.bottom - rect.top)) return false;
        paint = Paint(hwnd, mem->tHWND, rect.right - rect.left, rect.bottom - rect.top);
 
        return true;
    }
 
    DWORD APIENTRY start() {
        MSG msg;
 
        while (true) {
            if (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) {
                if (msg.message == WM_QUIT) break;
                TranslateMessage(&msg);
                DispatchMessage(&msg);
 
                GetWindowRect(mem->tHWND, &rect);
                int width = rect.right - rect.left;
                int height = rect.bottom - rect.top;
 
                MoveWindow(hwnd, rect.left, rect.top, width, height, true);
            }
            Sleep(1);
        }
 
        return (int)msg.wParam;
    }
 
    static DWORD WINAPI static_start(void* param) {
        Overlay* ol = (Overlay*)param;
        return ol->start();
    }
 
    static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
        switch (message) {
        case WM_PAINT:
            paint.render();
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
        return 0;
    }
};
#endif

memory.hpp :

#ifndef MEMORY_HPP
#define MEMORY_HPP
 
#pragma once
#pragma warning(disable: 6276)
 
#include <Windows.h>
#include <TlHelp32.h>
 
const wchar_t* TARGET = L"Telegram.exe"; // you may put any program here, like Notepad or Calculator
LPCSTR WINDOW_NAME = "Telegram";
 
class Memory {
public:
    DWORD tPID;
    HANDLE tProcess;
    HWND tHWND;
 
    Memory() {
        this->tPID = NULL;
        this->tProcess = NULL;
 
        if (!this->handle_process(TARGET)) return;
        this->tHWND = FindWindowA(0, WINDOW_NAME);
    }
 
    ~Memory() {
        CloseHandle(tProcess);
    }
 
    HANDLE handle_process(const wchar_t* processName) {
        HANDLE handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
        PROCESSENTRY32 entry;
        entry.dwSize = sizeof(entry);
 
        do {
            if (!wcscmp(entry.szExeFile, processName)) {
                tPID = entry.th32ProcessID;
                CloseHandle(handle);
                tProcess = OpenProcess(PROCESS_ALL_ACCESS, false, tPID);
                return tProcess;
            }
        } while (Process32Next(handle, &entry));
        CloseHandle(handle);
        return NULL;
    }
};
#endif

paint.hpp :

#ifndef PAINT_HPP
#define PAINT_HPP
#pragma once
#pragma warning(disable: 26495)
 
#include <Windows.h>
 
#include <d3d9.h>
#include <d3dx9.h> // Project -> [project name] Properties -> VC++ Directories: Add $(DXSDK_DIR)Include into Include Directories and $(DXSDK_DIR)Lib\x86 into Library directories
// Make sure you have Direct3D 9 SDK installed - https://www.microsoft.com/en-gb/download/details.aspx?id=6812
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
#pragma comment(lib, "legacy_stdio_definitions.lib")
 
class Paint {
private:
    IDirect3D9Ex* object = NULL; // used to create device
    IDirect3DDevice9Ex* device = NULL; // contains functions like begin and end scene 
    D3DPRESENT_PARAMETERS params;
    ID3DXFont* d3d_font = 0; // font used when displaying text
 
    HWND t_hwnd; // target process window
    int width; // target process width
    int height;
 
    int d3d9init(HWND hwnd) {
        if (FAILED(Direct3DCreate9Ex(D3D_SDK_VERSION, &object))) {
            DestroyWindow(hwnd);
        }
 
        ZeroMemory(&params, sizeof(params));
        params.BackBufferWidth = width;
        params.BackBufferHeight = height;
        params.Windowed = TRUE;
        params.SwapEffect = D3DSWAPEFFECT_DISCARD;
        params.hDeviceWindow = hwnd;
        params.MultiSampleQuality = D3DMULTISAMPLE_NONE;
        params.BackBufferFormat = D3DFMT_A8R8G8B8;
        params.EnableAutoDepthStencil = TRUE;
        params.AutoDepthStencilFormat = D3DFMT_D16;
 
        HRESULT res = object->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &params, 0, &device);
        if (FAILED(res)) DestroyWindow(hwnd);
 
        D3DXCreateFont(device, 50, 0, FW_BOLD, 1, false, DEFAULT_CHARSET, OUT_DEVICE_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, L"Consolas", &d3d_font);
 
        return 0;
    }
 
    void draw_text(char* text, int x, int y, int a, int r, int g, int b) {
        RECT rect;
        rect.left = x;
        rect.top = y;
        d3d_font->DrawTextA(0, text, strlen(text), &rect, DT_NOCLIP, D3DCOLOR_ARGB(a, r, g, b));
    }
 
public:
    Paint() {
        this->device = nullptr;
        this->object = nullptr;
    }
 
    ~Paint() {
        if (object != NULL) object->Release();
        if (device != NULL) device->Release();
    }
 
    Paint(HWND hwnd, HWND t_hwnd, int width, int height) {
        this->t_hwnd = t_hwnd;
        this->width = width;
        this->height = height;
        d3d9init(hwnd);
    }
 
    int render() {
        if (device == nullptr) return 1;
 
        device->Clear(0, 0, D3DCLEAR_TARGET, 0, 1.0f, 0);
        device->BeginScene();
 
        if (t_hwnd == GetForegroundWindow()) {
            draw_text((char*)"Test message", 15, 15, 255, 150, 150, 150);
        }
 
        device->EndScene();
        device->PresentEx(0, 0, 0, 0, 0);
 
        return 0;
    }
};
 
#endif

제대로 작동하지 않습니다. 어떤 아이디어, 내가 뭘 잘못하고 있니?
또한 디버깅을 시도 할 때 RegisterClassEx (overlay.hpp) 함수를 호출하는 지점에서 "0xC0000005 : 액세스 위반 읽기 위치 0xADE50000"오류가 발생한다는 이상한 점을 발견했습니다. WNDCLASSEX 구조의 모든 멤버를 초기화했지만 너무 이상합니다. 또한 프로젝트가 성공적으로 빌드되고 빌드 된 .exe 파일을 실행할 수 있다는 것은 매우 이상합니다.

답변

1 ZhuSong-MSFT Dec 04 2020 at 13:21

액세스 충돌의 원인은 구조 의 hInstance매개 변수 설정 때문입니다 WNDCLASSEX. 다음과 같이 설정할 수 있습니다 GetModuleHandle(NULL).

코드처럼 :

wc.hInstance = GetModuleHandle(NULL);

응용 프로그램이 응답하지 않는 이유는 스레드에 GUI 스레드를 설정하고 주 스레드에 무선 루프를 설정하여 UI 스레드가 응답하지 않기 때문입니다. 이 스레드를 참조 할 수 있습니다 .

start메인 스레드에서 함수 를 호출 한 다음 함수를 실행할 스레드를 생성하면 SendMessage이 문제를 해결할 수 있습니다.

void fun(const Overlay&ol)
{
    while (1) {
        SendMessage(ol.hwnd, WM_PAINT, NULL, NULL);
    }
}

int main() {
    Memory mem;
    Overlay ol(&mem);
    std::thread t(fun, ol);
    ol.static_start(&ol);
    //HANDLE overlay = CreateThread(NULL, NULL, &ol.static_start, (void*)&ol, NULL, NULL);
    

    t.join();
    return EXIT_SUCCESS;
}

편집하다

@David가 말했듯이 WM_PAINT메시지를 보내지 말고 start함수를 직접 호출하여 제대로 작동하십시오.