尝试在 C++ DLL 中挂钩 MessageBox 并在 C# 应用程序中使用它们

问题描述 投票:0回答:1

我在 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));
            }
        }
    }
}

当我尝试在调试模式下运行时,没有任何用处:

Debug output in Visual Studio Enterprise 2022

我尝试使用 System Informer 弄清楚发生了什么,然后我可以看到 WerFault.exe 正在运行,因为我的应用程序崩溃了:

System Informer check

当我试图检查正在发生的事情时,我得到了这个:

System Informer inspect

它说:

用户回调期间遇到未处理的异常。

但是我在调试时找不到任何有用的东西,这让我很困惑。


更新:

启用调试本机代码后,我可以看到错误是:

Education.exe 中的 0x00007FFD2766F7FA (ucrtbased.dll) 抛出异常:0xC0000005:读取位置 0x0000000000008002 时发生访问冲突。

在线:

if (lpcs->lpszClass && wcscmp(lpcs->lpszClass, L"#32770") == 0)
c++ .net winapi hook .net-8.0
1个回答
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) {
        ...
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.