【问题标题】:A window transparent overlay stops working after a few seconds窗口透明覆盖在几秒钟后停止工作
【发布时间】:2021-03-15 20:27:41
【问题描述】:

我正在尝试在某个进程的窗口上创建一个透明覆盖(我想使用 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

内存.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文件。

【问题讨论】:

  • 一遍又一遍地问同一个问题并不酷。您仍然没有在主线程中运行消息循环。你不在最后一个问题中。你现在不是。
  • @DavidHeffernan,我问了同样的问题,因为模组关闭了最后一个,即使我编辑了他们希望我编辑的内容。老实说,我不明白你说我没有在主线程中运行消息循环是什么意思。我在另一个线程中运行它并从主线程那里发送消息。我无法将消息循环放入主线程,因为我在原始程序中已经有很多循环和语句。此外,该程序在其他评论者的 PC 上运行良好。也许问题出在我的 IDE 或 smth 上?但无论如何我都会尝试将消息循环放入主线程。

标签: c++ windows winapi directx overlay


【解决方案1】:

访问冲突的原因是您的WNDCLASSEX 结构中hInstance 参数的设置。可以设置为GetModuleHandle(NULL)

就像代码一样:

wc.hInstance = GetModuleHandle(NULL);

应用程序没有响应的原因是你在线程中设置了GUI线程,在主线程中设置了无线循环,会导致UI线程没有响应。可以参考这个thread

只需要在主线程中调用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函数即可正常工作。

【讨论】:

  • 您无法发送 WM_PAINT 消息
  • @DavidHeffernan 是的,经过测试,我发现发送WM_PAINT消息的功能是多余的,可以直接删除。我写这篇文章是为了解释应用程序没有响应的原因。
  • 谢谢@ZhuSong-MSFT,它有效。你能告诉我为什么我不应该从并行线程发送 WM_PAINT 消息吗?
  • @Andrew 系统将负责发送WM_PAINT 消息。我测试了代码也可以正常工作,不用另外线程发送WM_PAINT。可以参考这个thread
  • 如果想添加无限循环,在主线程中发送消息,没有什么好办法。请参阅@Niall 在此thread 中的回答:将运行时间较长的任务移至后台或工作线程,然后轮询未来以完成,或者让线程将消息发回 GUI 以表示它已完成,然后检索结果必填。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-15
  • 2021-05-12
相关资源
最近更新 更多