3

ImGUI 1.87 绘制D3D外部菜单

 1 year ago
source link: https://blog.51cto.com/lyshark/5724841
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ImGUI 1.87 绘制D3D外部菜单

精选 原创

lyshark 2022-09-30 09:56:46 ©著作权

文章标签 d3 #include ide 文章分类 C/C++ 编程语言 阅读数245

ImGUI 它是与平台无关的C++轻量级跨平台图形界面库,没有任何第三方依赖,可以将ImGUI的源码直接加到项目中使用,该框架通常会配合特定的D3Dx9等图形开发工具包一起使用,ImGUI常用来实现进程内的菜单功能,而有些辅助开发作者也会使用该框架开发菜单页面,总体来说这是一个很不错的绘图库,如下将公开新版ImGUI如何实现绘制外部菜单的功能。

ImGUI官方下载地址: https://github.com/ocornut/imgui/releases

在使用ImGUI页面之前需要先来实现一个简单的附着功能,即如何将一个窗体附着到另一个窗体之上,其实代码很简单,如下所示当用户输入进程PID时,会自动跟随窗体并附着在窗体顶部。

#include <Windows.h>
#include <iostream>

struct handle_data
{
	unsigned long process_id;
	HWND best_handle;
};

// By: LyShark
BOOL IsMainWindow(HWND handle)
{
	return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);
}

BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam)
{
	// By: LyShark
	handle_data& data = *(handle_data*)lParam;
	unsigned long process_id = 0;
	GetWindowThreadProcessId(handle, &process_id);
	if (data.process_id != process_id || !IsMainWindow(handle)) {
		return TRUE;
	}
	data.best_handle = handle;
	return FALSE;
}

// By: LyShark
HWND FindMainWindow(unsigned long process_id)
{
	handle_data data;
	data.process_id = process_id;
	data.best_handle = 0;
	EnumWindows(EnumWindowsCallback, (LPARAM)&data);
	return data.best_handle;
}

int main(int argc, char* argv[])
{
	DWORD pid = 28396;

	std::cout << "输入进程PID: " << std::endl;
	std::cin >> pid;

	// 获取屏幕宽和高
	int iWidth = ::GetSystemMetrics(SM_CXSCREEN);
	int iHeight = ::GetSystemMetrics(SM_CYSCREEN);

	// 根据PID寻找游戏窗口
	HWND hwnd = FindMainWindow(pid);

	while (1)
	{
		SetTimer(hwnd, 1, 150, NULL);

		// 实现透明必须设置WS_EX_LAYERED标志
		LONG lWinStyleEx = GetWindowLong(hwnd, GWL_EXSTYLE);
		lWinStyleEx = lWinStyleEx | WS_EX_LAYERED;

		SetWindowLong(hwnd, GWL_EXSTYLE, lWinStyleEx);
		SetLayeredWindowAttributes(hwnd, 0, RGB(40, 40, 40), LWA_ALPHA);

		// 去掉标题栏及边框
		LONG_PTR Style = GetWindowLongPtr(hwnd, GWL_STYLE);
		Style = Style & ~WS_CAPTION & ~WS_SYSMENU & ~WS_SIZEBOX;
		SetWindowLongPtr(hwnd, GWL_STYLE, Style);

		// 至顶层窗口 最大化
		SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, iWidth, iHeight, SWP_SHOWWINDOW);

		// 设置窗体可穿透鼠标
		SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT | WS_EX_LAYERED);

		// 绘图
		HDC hdc = ::GetDC(hwnd);
		HDC mdc = ::CreateCompatibleDC(hdc);

		// 创建画笔
		HPEN hpen = CreatePen(PS_SOLID, 10, RGB(0, 255, 0));
		// DC 选择画笔
		SelectObject(hdc, hpen);
		// (画笔)从初始点移动到 50,50
		MoveToEx(hdc, 100, 100, NULL);
		// (画笔)从初始点画线到 100,100
		LineTo(hdc, 1000, 1000);

		RECT rect = {0};

		rect.bottom = 10;
		rect.left = 20;
		rect.right = 20;
		rect.top = 15;

		DrawText(hdc, L"hello lyshark.com", strlen("hello lyshark.com"), &rect, DT_CALCRECT | DT_CENTER | DT_SINGLELINE);
	}

	return 0;
}

绘制效果图:

ImGUI 1.87 绘制D3D外部菜单_d3

接着我们使用Imgui绘制一个动态菜单,首先下载imgui并打开项目中的examples目录,找到example_win32_directx9打开后自己配置好dx9SDK开发工具包。

ImGUI 1.87 绘制D3D外部菜单_ide_02

代码直接调用,并附加到Counter-Strike Source游戏窗体之上即可,这段代码也很简单。

#include "imgui.h"
#include "imgui_impl_dx9.h"
#include "imgui_impl_win32.h"

#include <d3d9.h>
#include <tchar.h>
#include <iostream>

#pragma execution_character_set("utf-8")

// 全局变量
// lyshark.com
static HWND hwnd;
static HWND GameHwnd;
static RECT WindowRectangle;
static int WindowWide, WindowHeight;

static LPDIRECT3D9 g_pD3D = NULL;
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
static D3DPRESENT_PARAMETERS g_d3dpp = {};

// 单选框设置状态
bool show_another_window = false;

// imgui 回调函数
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// By: LyShark
bool CreateDeviceD3D(HWND hWnd)
{
	if ((g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
	{
		return false;
	}

	ZeroMemory(&g_d3dpp, sizeof(g_d3dpp));
	g_d3dpp.Windowed = TRUE;
	g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	g_d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	g_d3dpp.EnableAutoDepthStencil = TRUE;
	g_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
	g_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;

	if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &g_d3dpp, &g_pd3dDevice) < 0)
	{
		return false;
	}
	return true;
}

void CleanupDeviceD3D()
{
	if (g_pd3dDevice)
	{
		g_pd3dDevice->Release();
		g_pd3dDevice = NULL;
	}

	if (g_pD3D)
	{
		g_pD3D->Release();
		g_pD3D = NULL;
	}
}

void ResetDevice()
{
	ImGui_ImplDX9_InvalidateDeviceObjects();
	HRESULT hr = g_pd3dDevice->Reset(&g_d3dpp);
	if (hr == D3DERR_INVALIDCALL)
	{
		IM_ASSERT(0);
	}
	ImGui_ImplDX9_CreateDeviceObjects();
}

LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
	{
		return true;
	}

	switch (msg)
	{
	case WM_SIZE:
		if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED)
		{
			g_d3dpp.BackBufferWidth = LOWORD(lParam);
			g_d3dpp.BackBufferHeight = HIWORD(lParam);
			ResetDevice();
		}
		return 0;

	case WM_SYSCOMMAND:
		if ((wParam & 0xfff0) == SC_KEYMENU)
		{
			return 0;
		}
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hWnd, msg, wParam, lParam);
}

// 绘制主方法
// www.cnblogs.com/lyshark
void DrawImGUI()
{
	// 启动IMGUI自绘
	ImGui_ImplDX9_NewFrame();
	ImGui_ImplWin32_NewFrame();
	ImGui::NewFrame();

	static float f = 0.0f;
	static int counter = 0;
	static char sz[256] = { 0 };

	ImGui::Begin("LyShark 辅助GUI主菜单");
	ImGui::Text("这是一段测试字符串");
	ImGui::Checkbox("弹出子窗口", &show_another_window);
	ImGui::SliderFloat("浮点条", &f, 0.0f, 1.0f);

	ImGui::InputText("输入内容", sz, 256, 0, 0, 0);

	if (ImGui::Button("点我触发"))
		counter++;
	ImGui::SameLine();
	ImGui::Text("触发次数 = %d", counter);

	ImGui::Text("当前FPS: %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
	ImGui::End();

	if (show_another_window)
	{
		ImGui::Begin("我是子窗体", &show_another_window);
		ImGui::Text(" 您好,LyShark !");
		if (ImGui::Button("关闭窗体"))
			show_another_window = false;
		ImGui::End();
	}
	ImGui::EndFrame();
}

// 自身窗口循环事件
void WindowMessageLoop()
{
	bool done = false;
	while (!done)
	{
		// 每次都将窗体置顶并跟随游戏窗体移动
		GetWindowRect(GameHwnd, &WindowRectangle);
		WindowWide = (WindowRectangle.right) - (WindowRectangle.left);
		WindowHeight = (WindowRectangle.bottom) - (WindowRectangle.top);
		DWORD dwStyle = GetWindowLong(GameHwnd, GWL_STYLE);
		if (dwStyle & WS_BORDER)
		{
			WindowRectangle.top += 23;
			WindowHeight -= 23;
		}

		// 跟随窗口移动
		MoveWindow(hwnd, WindowRectangle.left + 8, WindowRectangle.top + 8, WindowWide - 11, WindowHeight - 11, true);

		// 开始消息循环
		MSG msg;
		while (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
			if (msg.message == WM_QUIT)
			{
				done = true;
			}
		}

		if (done)
		{
			break;
		}

		// 开始绘制
		DrawImGUI();

		g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
		g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
		g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
		g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0, 1.0f, 0);

		if (g_pd3dDevice->BeginScene() >= 0)
		{
			ImGui::Render();
			ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
			g_pd3dDevice->EndScene();
		}

		HRESULT result = g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
		if (result == D3DERR_DEVICELOST && g_pd3dDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET)
		{
			ResetDevice();
		}
	}
}

int main(int argc, char *argv[])
{
	// 注册窗体类
	WNDCLASSEX wc;

	// 附加到指定窗体上
	wc.cbClsExtra = NULL;
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.cbWndExtra = NULL;
	wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 0, 0));
	wc.hCursor = LoadCursor(0, IDC_ARROW);
	wc.hIcon = LoadIcon(0, IDI_APPLICATION);
	wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
	wc.hInstance = GetModuleHandle(NULL);
	wc.lpfnWndProc = (WNDPROC)WndProc;
	wc.lpszClassName = L" ";
	wc.lpszMenuName = L" ";
	wc.style = CS_VREDRAW | CS_HREDRAW;

	RegisterClassEx(&wc);

	// 得到窗口句柄
	GameHwnd = FindWindowA(NULL, "Counter-Strike Source");
	GetWindowRect(GameHwnd, &WindowRectangle);
	WindowWide = WindowRectangle.right - WindowRectangle.left;
	WindowHeight = WindowRectangle.bottom - WindowRectangle.top;

	// 创建窗体
	hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW, L" ", L" ", WS_POPUP, 1, 1, WindowWide, WindowHeight, 0, 0, wc.hInstance, 0);

	// 显示窗口
	SetLayeredWindowAttributes(hwnd, 0, RGB(0, 0, 0), LWA_ALPHA);
	SetLayeredWindowAttributes(hwnd, 0, RGB(0, 0, 0), LWA_COLORKEY);
	ShowWindow(hwnd, SW_SHOW);

	// 初始化D3D
	if (!CreateDeviceD3D(hwnd))
	{
		CleanupDeviceD3D();
		UnregisterClass(wc.lpszClassName, wc.hInstance);
		return 0;
	}

	// 更新窗体
	UpdateWindow(hwnd);

	// 初始化ImGUI
	ImGui::CreateContext();
	ImGuiIO& io = ImGui::GetIO(); (void)io;
	io.Fonts->AddFontFromFileTTF("c:/windows/fonts/simhei.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesChineseSimplifiedCommon());

	ImGui::StyleColorsDark();
	ImGui_ImplWin32_Init(hwnd);
	ImGui_ImplDX9_Init(g_pd3dDevice);

	// 开始执行绘制循环事件
	WindowMessageLoop();

	ImGui_ImplDX9_Shutdown();
	ImGui_ImplWin32_Shutdown();
	ImGui::DestroyContext();

	CleanupDeviceD3D();
	DestroyWindow(hwnd);
	UnregisterClass(wc.lpszClassName, wc.hInstance);
	return 0;
}

绘制效果如下:

ImGUI 1.87 绘制D3D外部菜单_#include_03

另外,Imgui也支持绘制到整个屏幕上,也可以当作全局GUI界面来使用。

#include "imgui.h"
#include "imgui_impl_dx9.h"
#include "imgui_impl_win32.h"

#include <d3d9.h>
#include <tchar.h>
#include <iostream>

#pragma execution_character_set("utf-8")

// 全局变量
static HWND hwnd;
static HWND GameHwnd;
static RECT WindowRectangle;
static int WindowWide, WindowHeight;

static LPDIRECT3D9 g_pD3D = NULL;
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
static D3DPRESENT_PARAMETERS g_d3dpp = {};

// 单选框设置状态
bool show_another_window = false;

// imgui 回调函数
// By: LyShark
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

bool CreateDeviceD3D(HWND hWnd)
{
	if ((g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
	{
		return false;
	}

	ZeroMemory(&g_d3dpp, sizeof(g_d3dpp));
	g_d3dpp.Windowed = TRUE;
	g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	g_d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	g_d3dpp.EnableAutoDepthStencil = TRUE;
	g_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
	g_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;

	if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &g_d3dpp, &g_pd3dDevice) < 0)
	{
		return false;
	}
	return true;
}

void CleanupDeviceD3D()
{
	if (g_pd3dDevice)
	{
		g_pd3dDevice->Release();
		g_pd3dDevice = NULL;
	}

	if (g_pD3D)
	{
		g_pD3D->Release();
		g_pD3D = NULL;
	}
}
// https://www.cnblogs.com/lyshark
void ResetDevice()
{
	ImGui_ImplDX9_InvalidateDeviceObjects();
	HRESULT hr = g_pd3dDevice->Reset(&g_d3dpp);
	if (hr == D3DERR_INVALIDCALL)
	{
		IM_ASSERT(0);
	}
	ImGui_ImplDX9_CreateDeviceObjects();
}

LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
	{
		return true;
	}

	switch (msg)
	{
	case WM_SIZE:
		if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED)
		{
			g_d3dpp.BackBufferWidth = LOWORD(lParam);
			g_d3dpp.BackBufferHeight = HIWORD(lParam);
			ResetDevice();
		}
		return 0;

	case WM_SYSCOMMAND:
		if ((wParam & 0xfff0) == SC_KEYMENU)
		{
			return 0;
		}
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hWnd, msg, wParam, lParam);
}

// 绘制主方法
// lyshark.com
void DrawImGUI()
{
	// 启动IMGUI自绘
	ImGui_ImplDX9_NewFrame();
	ImGui_ImplWin32_NewFrame();
	ImGui::NewFrame();

	static float f = 0.0f;
	static int counter = 0;
	static char sz[256] = { 0 };

	ImGui::Begin("LyShark 辅助GUI主菜单");
	ImGui::Text("这是一段测试字符串");
	ImGui::Checkbox("弹出子窗口", &show_another_window);
	ImGui::SliderFloat("浮点条", &f, 0.0f, 1.0f);

	ImGui::InputText("输入内容", sz, 256, 0, 0, 0);

	if (ImGui::Button("点我触发"))
		counter++;
	ImGui::SameLine();
	ImGui::Text("触发次数 = %d", counter);

	ImGui::Text("当前FPS: %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
	ImGui::End();

	if (show_another_window)
	{
		ImGui::Begin("我是子窗体", &show_another_window);
		ImGui::Text(" 您好,LyShark !");
		if (ImGui::Button("关闭窗体"))
			show_another_window = false;
		ImGui::End();
	}
	ImGui::EndFrame();
}

// 自身窗口循环事件
void WindowMessageLoop()
{
	bool done = false;
	while (!done)
	{
		// 每次都将窗体置顶并跟随游戏窗体移动
		GetWindowRect(GameHwnd, &WindowRectangle);
		WindowWide = (WindowRectangle.right) - (WindowRectangle.left);
		WindowHeight = (WindowRectangle.bottom) - (WindowRectangle.top);
		DWORD dwStyle = GetWindowLong(GameHwnd, GWL_STYLE);
		if (dwStyle & WS_BORDER)
		{
			WindowRectangle.top += 23;
			WindowHeight -= 23;
		}

		// 跟随窗口移动
		MoveWindow(hwnd, WindowRectangle.left + 8, WindowRectangle.top + 8, WindowWide - 11, WindowHeight - 11, true);

		// 开始消息循环
		MSG msg;
		while (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
			if (msg.message == WM_QUIT)
			{
				done = true;
			}
		}

		if (done)
		{
			break;
		}

		// 开始绘制
		DrawImGUI();

		g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
		g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
		g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
		g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0, 1.0f, 0);

		if (g_pd3dDevice->BeginScene() >= 0)
		{
			ImGui::Render();
			ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
			g_pd3dDevice->EndScene();
		}

		HRESULT result = g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
		if (result == D3DERR_DEVICELOST && g_pd3dDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET)
		{
			ResetDevice();
		}
	}
}

int main(int argc, char *argv[])
{
	// 注册窗体类
	WNDCLASSEX wc;

	// 附加到整个屏幕上
	wc.cbClsExtra = NULL;
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.cbWndExtra = NULL;
	wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 0, 0));
	wc.hCursor = LoadCursor(0, IDC_ARROW);
	wc.hIcon = LoadIcon(0, IDI_APPLICATION);
	wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
	wc.hInstance = GetModuleHandle(NULL);
	wc.lpfnWndProc = (WNDPROC)WndProc;
	wc.lpszClassName = L" ";
	wc.lpszMenuName = L" ";
	wc.style = CS_VREDRAW | CS_HREDRAW;
	::RegisterClassEx(&wc);

	// 屏幕宽度和高度
	WindowWide = GetSystemMetrics(SM_CXSCREEN);
	WindowHeight = GetSystemMetrics(SM_CYSCREEN);

	// 创建窗体
	HWND hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW, L" ", L" ", WS_POPUP, 1, 1, WindowWide, WindowHeight, 0, 0, wc.hInstance, 0);

	// 显示窗口
	SetLayeredWindowAttributes(hwnd, 0, 1.0f, LWA_ALPHA);
	SetLayeredWindowAttributes(hwnd, 0, RGB(0, 0, 0), LWA_COLORKEY);
	ShowWindow(hwnd, SW_SHOW);

	// 初始化D3D
	if (!CreateDeviceD3D(hwnd))
	{
		CleanupDeviceD3D();
		UnregisterClass(wc.lpszClassName, wc.hInstance);
		return 0;
	}

	// 更新窗体
	UpdateWindow(hwnd);

	// 初始化ImGUI
	ImGui::CreateContext();
	ImGuiIO& io = ImGui::GetIO(); (void)io;
	io.Fonts->AddFontFromFileTTF("c:/windows/fonts/simhei.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesChineseSimplifiedCommon());

	ImGui::StyleColorsDark();
	ImGui_ImplWin32_Init(hwnd);
	ImGui_ImplDX9_Init(g_pd3dDevice);

	// 开始执行绘制循环事件
	WindowMessageLoop();

	ImGui_ImplDX9_Shutdown();
	ImGui_ImplWin32_Shutdown();
	ImGui::DestroyContext();

	CleanupDeviceD3D();
	DestroyWindow(hwnd);
	UnregisterClass(wc.lpszClassName, wc.hInstance);
	return 0;
}

绘制效果如下:

ImGUI 1.87 绘制D3D外部菜单_d3_04

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK