我在 Windows 11 x64 23H2 上运行 Visual Studio Enterprise 2022。
我正在尝试在 C++ 中挂钩
MessageBox
以将其背景颜色更改为 #457B9D
并将其前景色更改为白色。另外,我想将按钮文本和按钮边框更改为白色,将背景颜色更改为透明。
之后,我将在我的 C# .NET 8.0 Windows Forms 应用程序中使用它,因为我不想为
MessageBox
创建新的表单,因为我们的项目已经变得如此复杂,修复每个表单中的代码是时间消耗(我总共有 90 个表单和 75 个用户控件)。
这是我的代码:
框架.h
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
#include <stdlib.h>
pch.h
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#include "framework.h"
#endif //PCH_H
pch.cpp
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
dllmain.cpp
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
extern "C" __declspec(dllexport) void HookMessageBoxW();
extern "C" __declspec(dllexport) void UnhookMessageBoxW();
const COLORREF bgColor = RGB(69, 123, 157); // #457B9D
const COLORREF textColor = RGB(255, 255, 255); // White
HHOOK hHook = NULL;
WNDPROC oldButtonProc = NULL;
// Button subclass procedure
LRESULT CALLBACK ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
try {
switch (uMsg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rect;
GetClientRect(hWnd, &rect);
// Create and use a brush for background color
HBRUSH hBrush = CreateSolidBrush(bgColor);
FillRect(hdc, &rect, hBrush);
DeleteObject(hBrush); // Delete the brush to avoid resource leaks
SetTextColor(hdc, textColor);
SetBkMode(hdc, TRANSPARENT);
// Draw the text on the button
WCHAR text[512];
GetWindowText(hWnd, text, 512);
DrawText(hdc, text, -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// Draw a white border around the button
HPEN hPen = CreatePen(PS_SOLID, 2, textColor);
HGDIOBJ oldPen = SelectObject(hdc, hPen);
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
SelectObject(hdc, oldPen);
DeleteObject(hPen); // Delete the pen to avoid resource leaks
EndPaint(hWnd, &ps);
return 0;
}
default:
break;
}
}
catch (...) {
// Log the exception or handle it accordingly
return CallWindowProc(oldButtonProc, hWnd, uMsg, wParam, lParam);
}
// Default processing for other messages
return CallWindowProc(oldButtonProc, hWnd, uMsg, wParam, lParam);
}
// MessageBox subclass procedure
LRESULT CALLBACK MessageBoxSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
try {
HDC hdcStatic;
HBRUSH hBrush = CreateSolidBrush(bgColor);
switch (uMsg) {
case WM_CTLCOLORDLG:
case WM_CTLCOLORSTATIC:
case WM_CTLCOLORBTN:
hdcStatic = (HDC)wParam;
SetTextColor(hdcStatic, textColor);
SetBkColor(hdcStatic, bgColor);
DeleteObject(hBrush); // Make sure to delete the brush after use
return (LRESULT)hBrush;
case WM_INITDIALOG: {
HWND hButton = GetDlgItem(hWnd, IDOK);
if (hButton) {
oldButtonProc = (WNDPROC)SetWindowLongPtr(hButton, GWLP_WNDPROC, (LONG_PTR)ButtonSubclassProc);
}
hButton = GetDlgItem(hWnd, IDCANCEL);
if (hButton) {
SetWindowLongPtr(hButton, GWLP_WNDPROC, (LONG_PTR)ButtonSubclassProc);
}
break;
}
case WM_DESTROY:
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)GetWindowLongPtr(hWnd, GWLP_USERDATA));
SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
break;
default:
break;
}
DeleteObject(hBrush); // Delete the brush before returning
}
catch (...) {
// Handle any exceptions to prevent crashes
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
// Default processing
return CallWindowProc((WNDPROC)GetWindowLongPtr(hWnd, GWLP_USERDATA), hWnd, uMsg, wParam, lParam);
}
// Hook procedure to capture MessageBox creation
LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam) {
try {
if (nCode == HCBT_CREATEWND) {
LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs;
// Check if it's a MessageBox
if (lpcs->lpszClass && wcscmp(lpcs->lpszClass, L"#32770") == 0) {
HWND hWnd = (HWND)wParam;
SetWindowLongPtr(hWnd, GWLP_USERDATA, SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MessageBoxSubclassProc));
}
}
}
catch (...) {
// Handle the exception gracefully
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
// Exported function to hook MessageBoxW
extern "C" __declspec(dllexport) void HookMessageBoxW() {
hHook = SetWindowsHookEx(WH_CBT, CBTProc, nullptr, GetCurrentThreadId());
}
// Exported function to unhook MessageBoxW
extern "C" __declspec(dllexport) void UnhookMessageBoxW() {
if (hHook) {
UnhookWindowsHookEx(hHook);
hHook = nullptr;
}
}
程序.cs
using OSVersionExtension;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Education
{
internal static class Program
{
[DllImport("Win32.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void HookMessageBoxW();
[DllImport("Win32.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void UnhookMessageBoxW();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool SetProcessDPIAware();
static void Main()
{
HookMessageBoxW();
ApplicationConfiguration.Initialize();
//SetProcessDPIAware();
// Check Operating System section
//
// We only have rounded corners (actually is uDWM hack) on Windows 11
// On earlier OS version, we need to apply our custom rounded corner
// that defined in EllipseControl.cs
//
// To check Windows version, we use OSCheckExt from NuGet package
// manager
//
// So, we have these cases:
//
// Case 1: If users have Windows 11: Let's use native uDWM hack (inside
// dwmapi.dll) and opt in system rounded corners
//
// Case 2: If users doesn't have Windows 11: We need to create
// custom interface to enable rounded corners that defined
// in EllipseControl.cs then enable them in Form1.cs
//
// Note that on Windows Server 2022, we still doesn't have uDWM hack,
// actually uDWM hack exists only on Windows 11. So if we detected
// Windows Server Edition, we have to use our custom rounded corners
// defined in EllipseControl.cs to enable rounded corners effect
//
// 9/3/2024
OSVersionExtension.OperatingSystem osFetchData = OSVersion.GetOperatingSystem();
// Windows 11 detected
if (osFetchData == OSVersionExtension.OperatingSystem.Windows11)
{
Application.Run(new Education_MainForm(true));
}
else
{
Application.Run(new Education_MainForm(false));
}
}
}
}
当我尝试在调试模式下运行时,没有任何用处:
我尝试使用 System Informer 弄清楚发生了什么,然后我可以看到 WerFault.exe 正在运行,因为我的应用程序崩溃了:
当我试图检查正在发生的事情时,我得到了这个:
它说:
用户回调期间遇到未处理的异常。
但是我在调试时找不到任何有用的东西,这让我很困惑。
更新:
启用调试本机代码后,我可以看到错误是:
Education.exe 中的 0x00007FFD2766F7FA (ucrtbased.dll) 抛出异常:0xC0000005:读取位置 0x0000000000008002 时发生访问冲突。
在线:
if (lpcs->lpszClass && wcscmp(lpcs->lpszClass, L"#32770") == 0)
启用调试本机代码后,我可以看到错误是:
Education.exe 中的 0x00007FFD2766F7FA (ucrtbased.dll) 抛出异常:0xC0000005:读取位置 0x0000000000008002 时发生访问冲突。
在线:
if (lpcs->lpszClass && wcscmp(lpcs->lpszClass, L"#32770") == 0)
CREATESTRUCT
文档:
lpsz类
类型:LPCTSTR
指向空终止字符串或原子的指针,指定新窗口的类名。
您的代码假设
lpszClass
始终是一个字符串,而不是系统对话框倾向于使用的原子。 试试这个:
if (lpcs->lpszClass)
{
if (IS_INTRESOURCE(lpcs->lpszClass))
{
if (reinterpret_cast<int>(lpcs->lpszClass) == 32770) {
...
}
}
else if (wcscmp(lpcs->lpszClass, L"#32770") == 0) {
...
}
}