Przezroczysta nakładka okna przestaje działać po kilku sekundach
Próbuję stworzyć przezroczystą nakładkę na jakieś okno procesu (chcę narysować trochę rzeczy na nakładce za pomocą Direct3D 9).
Nakładka jest tworzona za pomocą zewnętrznego programu (nie biblioteki wstrzykniętej .dll lub czegoś podobnego).
Problem polega na tym, że kiedy uruchamiam program, pojawia się niewidoczna nakładka nad oknem procesu (a nawet rysuje na nim jakiś tekst, więc WM_PAINT wydaje się działać), ale w ciągu następnych kilku sekund kursor staje się "zegarem piaskowym" stylizowany (przepraszam, nie wiem, jak inaczej nazywa się xd) i jeśli kliknę w okno, pokazuje błąd „Aplikacja nie odpowiada” i staje się jednolicie biały.

Importuję klasę nakładki do pliku punktu wejścia i oto jak ją uruchomić w funkcji głównej (uproszczona):
#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(¶ms, 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, ¶ms, 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
Po prostu nie działa poprawnie. Jakieś pomysły, co robię źle?
Zauważyłem również dziwną rzecz, że kiedy próbuję debugować, wyświetla mi się błąd „0xC0000005: Naruszenie dostępu odczytującego lokalizację 0xADE50000” w miejscu, w którym wywołuję funkcję RegisterClassEx (overlay.hpp). Tak dziwne, mimo że zainicjowałem wszystkie elementy struktury WNDCLASSEX. Dziwne jest również to, że projekt buduje się pomyślnie i mogę uruchomić zbudowany plik .exe.
Odpowiedzi
Przyczyną konfliktu dostępu jest ustawienie hInstance
parametru w Twojej WNDCLASSEX
strukturze. Możesz ustawić to na GetModuleHandle(NULL)
;
Tak jak kod:
wc.hInstance = GetModuleHandle(NULL);
Powodem, dla którego aplikacja nie odpowiada, jest ustawienie wątku GUI w wątku i ustawienie pętli bezprzewodowej w wątku głównym, co spowoduje, że wątek interfejsu użytkownika nie będzie odpowiadał. Możesz odnieść się do tego wątku .
Wystarczy wywołać start
funkcję w głównym wątku, a następnie utworzyć wątek do wykonania SendMessage
funkcji, aby rozwiązać ten problem:
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;
}
Edytować
Jak powiedział @David, nie powinieneś wysyłać WM_PAINT
wiadomości, po prostu wywołaj start
funkcję bezpośrednio, aby działała poprawnie.