Una superposición transparente de ventana deja de funcionar después de unos segundos

Dec 04 2020

Estoy tratando de crear una superposición transparente sobre la ventana de algún proceso (quiero dibujar algunas cosas en la superposición usando Direct3D 9).

La superposición se crea utilizando un programa externo (no una biblioteca inyectada .dll o algo así).

El problema es que cuando lanzo el programa, aparece una superposición invisible sobre la ventana del proceso (e incluso dibuja algo de texto en ella, por lo que WM_PAINT parece estar funcionando), pero en los siguientes segundos el cursor se convierte en un "reloj de arena" con estilo (lo siento, no sé de qué otra manera se llama xd) y si hago clic en la ventana, muestra un error "La aplicación no responde" y se vuelve blanco fijo.

Importo la clase de superposición en el archivo de punto de entrada y así es como la ejecuto en la función principal (simplificada):

#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

Simplemente no funciona correctamente. Alguna idea, ¿qué estoy haciendo mal?
También he notado algo extraño, que cuando trato de depurar, me da un error "0xC0000005: ubicación de lectura de violación de acceso 0xADE50000" en el punto donde llamo a la función RegisterClassEx (overlay.hpp). Muy extraño, aunque inicialicé todos los miembros de la estructura WNDCLASSEX. También es bastante extraño que el proyecto se compile correctamente y pueda ejecutar el archivo .exe compilado.

Respuestas

1 ZhuSong-MSFT Dec 04 2020 at 13:21

El motivo del conflicto de acceso es la configuración del hInstanceparámetro en su WNDCLASSEXestructura. Puede configurarlo en GetModuleHandle(NULL);

Al igual que el código:

wc.hInstance = GetModuleHandle(NULL);

La razón por la que la aplicación no responde es que usted configura el subproceso de la GUI en el subproceso y configura el bucle inalámbrico en el subproceso principal, lo que hará que el subproceso de la interfaz de usuario no responda. Puede consultar este hilo .

Solo necesita llamar a la startfunción en el hilo principal, y luego crear un hilo para ejecutar la SendMessagefunción puede resolver este problema:

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

Editar

Como dijo @David, no debe enviar el WM_PAINTmensaje, simplemente llame a la startfunción directamente para que funcione correctamente.