Я использую Windows 11 x64 23H2 с Visual Studio Enterprise 2022.
Я пытаюсь подключить MessageBox
в C++, чтобы изменить цвет фона на #457B9D
и изменить цвет переднего плана на белый. Кроме того, я хочу изменить текст кнопки и границу кнопки на белый, а цвет фона на прозрачный.
После этого я буду использовать это в своем приложении Windows Forms на C# .NET 8.0, потому что я не хочу создавать новую форму для MessageBox
, потому что наш проект стал настолько сложным, что исправление кода в каждой форме требует много времени (я иметь в общей сложности 90 форм и 75 пользовательских элементов управления).
Вот мой код:
framework.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: 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 работает, потому что мое приложение упало:
Когда я попытался проверить, что происходит, я получил следующее:
Там говорится:
Во время обратного вызова пользователя обнаружено необработанное исключение.
Но при отладке я не могу найти ничего полезного, что меня очень сбивает с толку.
ОБНОВЛЯТЬ:
После включения собственного кода отладки я вижу ошибку:
Исключение, возникающее по адресу 0x00007FFD2766F7FA (ucrtbased.dll) в Education.exe: 0xC0000005: местоположение чтения нарушения прав доступа 0x0000000000008002.
в строке:
if (lpcs->lpszClass && wcscmp(lpcs->lpszClass, L"#32770") == 0)
@JohnOmielan спасибо за ваш ответ, я удалил DeleteObject(), но, к сожалению, все еще не работает. Я потратил несколько часов, чтобы выяснить, что происходит, но безуспешно… Думаю, мне нужно вернуться к документации Windows, но, к сожалению, я не смог найти там решения….
Родной отладчик должен что-то показать. Возможно, необходимо изменить некоторые параметры, например. обработка исключений, особенно Win32 (Отладка->Windows->Исключения). Я проверю, что все указатели содержат ожидаемые значения (oldButtonProc
, GWLP_USERDATA
, GWLP_WNDPROC
).
@DanielSęk, спасибо, теперь я вижу ошибку в строке if (lpcs->lpszClass && wcscmp(lpcs->lpszClass, L"#32770") == 0) {
с ошибкой: Исключение, созданное по адресу 0x00007FFD2766F7FA (ucrtbased.dll) в Education.exe: 0xC0000005: Местоположение чтения нарушения прав доступа 0x0000000000008002.
Рассмотрите возможность использования менее хрупкого способа управления подклассами. Вам также необходимо прекратить использование GWLP_USERDATA
; это не твое.
Черт возьми, как эта строка могла привести к нарушению доступа по адресу 0x00008002? Может быть, lpszClass
— это 0x8002? Это допустимое значение для lpszClass
(но не wcscmp
): Согласно документации, lpszClass
является «указателем на строку с нулевым завершением или атом, который определяет имя класса нового окна».
32770 == 0x8002
Используйте макрос IS_INTRESOURCE, чтобы различать имя класса и атом. Убедитесь, что вы понимаете, что документация лжет вам, когда говорится: «Возвращаемое значение: нет».
Вот что меня смущает. Сейчас я думаю о том, что, возможно, защита от исправлений ядра или антивирус не позволяют моему приложению перехватить мой MessageBox. Но я работаю в пользовательской среде, а не в ядре, и даже если я отключу антивирус, запущу от имени администратора или NT AUTHORITY\SYSTEM, ошибка все равно возникнет. Кроме того, я подключаю MessageBox только к текущему процессу, он не перехватывает всю систему. Кроме того, XMessageBox — декомпилированная версия API MessageBox, относящаяся к Windows XP, она до сих пор работает в Windows 11, так что, возможно, мой код делает что-то глупое.
После включения собственного кода отладки я вижу ошибку:
Исключение, возникающее по адресу 0x00007FFD2766F7FA (ucrtbased.dll) в Education.exe: 0xC0000005: местоположение чтения нарушения прав доступа 0x0000000000008002.
в строке:
if (lpcs->lpszClass && wcscmp(lpcs->lpszClass, L"#32770") == 0)
Согласно документации CREATESTRUCT:
лпсзкласс
Тип: ЛПКТСТР
Указатель на строку с нулевым завершением или атом, определяющий имя класса нового окна.
В вашем коде предполагается, что lpszClass
всегда является указателем на строку, завершающуюся нулем, а не атомом, который действительно используется в системных диалогах.
Вы можете использовать макрос IS_INTRESOURCE()
, чтобы различать их, например:
if (lpcs->lpszClass)
{
if (IS_INTRESOURCE(lpcs->lpszClass))
{
if (reinterpret_cast<int>(lpcs->lpszClass) == 32770) {
...
}
/* alternatively:
if (lpcs->lpszClass == MAKEINTATOM(32770)) {
...
}
*/
}
else if (wcscmp(lpcs->lpszClass, L"#32770") == 0) {
...
}
}
К сожалению, нарушения прав доступа остались, теперь они есть в kernelbase.dll. Я думаю, что у меня недостаточно привилегий, но когда я пытался работать от имени администратора или NT AUTHORITY\SYSTEM, программа все равно вылетает, и ошибка та же самая.... Я думаю, мне, возможно, придется реализовать MessageBox API самостоятельно. или декомпилировать API для использования недокументированной функции, но я думаю, что это тоже нехорошо
После 3 дней отладки (включая опробование всех методов, которые я придумал: проверка границ, проверка нулевого указателя, проверка ссылок...), но все еще застрял на нарушении прав доступа, я нашел решение, которое кажется более совершенным, чем один хук. Однако, чтобы заставить его работать, потребуется немало манипуляций, но я думаю, оно того стоит, особенно когда оно работает идеально.
Шаги будут довольно длинными, но я постараюсь быть максимально подробным. Короче говоря, я использовал XMessageBox, реверс-инжиниринг API Windows MessageBox, который восходит к эпохе Windows XP и Windows Vista и работает до сих пор. К счастью, Microsoft, вероятно, сохранила API MessageBox нетронутым и по сей день.
Я надеюсь, что это поможет людям, у которых такая же проблема, как и у меня. Если вы все еще просите решение, подключите MessageBox
API: Извините, вы больше не можете этого сделать.
Вот подробности:
vs6\Release
и запустите XMsgBoxTest.exe
. Это будет то, с чего мы начнемGallery...
Нажмите на Custom Icon + Custom Colors
, это то, что нам нужно.
Это то, что нам нужно. Теперь надо придумать, как это применить.... Давайте посмотрим на это поле. Это код, который нам нужен для отображения MessageBox, как показано в примере выше.
Теперь нам нужно СОХРАНИТЬ этот код для использования в будущем. Теперь вернитесь в Visual Studio и создайте новый проект C++ DLL MFC:
Дайте ему любое имя, но убедитесь, что оно находится в том же решении. После создания получим вот это:
Теперь вернитесь с папкой XMessageBox и перейдите в каталог src
. Вы должны увидеть это:
Теперь нам нужно получить необходимые файлы. Вот эти файлы необходимы:
ЗАГОЛОВОЧНЫЕ ФАЙЛЫ
XHyperLink.h
XMessageBox.h
XTrace.h
ИСХОДНЫЕ ФАЙЛЫ
XHyperLink.cpp
XMessageBox.cpp
Вот файлы, которые нам нужны:
Скопируйте их в каталог исходного кода нашего проекта C++ MFC DLL Project, после этого мы получим следующее:
Вернитесь в Visual Studio, щелкните правой кнопкой мыши Header Files
в проекте C++ MFC DLL, затем выберите Add > Existing Item...
.
затем выберите все наши ЗАГОЛОВОЧНЫЕ ФАЙЛЫ
После этого сделайте то же самое с исходными файлами. После этого вы получите такую структуру проекта:
Теперь, что не важно: перейдите к Win32MFC.cpp
(обратите внимание, что имя файла будет меняться в зависимости от имени вашего проекта), затем прокомментируйте весь код внутри них, затем добавьте #include "pch.h"
в этот файл. Это очень важно:
Сделайте то же самое с файлом заголовка:
Теперь вернитесь в каталог src
XMessageBox, откройте файл StdAfx.h
в вашем любимом редакторе, я буду использовать блокнот:
Скопируйте все коды, чтобы заменить текущее содержимое вашего framework.h
:
Теперь откройте свои pch.h
файлы. Затем добавьте этот код после #include "framework.h"
:
#include "XHyperLink.h"
#include "XMessageBox.h"
#include "XTrace.h"
Ваш файл должен выглядеть так:
Теперь откройте файлы XMessageBox.h
и найдите это:
int XMessageBox(HWND hwnd,
LPCTSTR lpszMessage,
LPCTSTR lpszCaption = NULL,
UINT nStyle = MB_OK | MB_ICONEXCLAMATION,
XMSGBOXPARAMS* pXMB = NULL);
Это основная функция, которую нам нужно вызвать для отображения нашего пользовательского MessageBox. Их нам тоже нужно экспортировать. Просто замените функцию на:
extern "C" __declspec(dllexport) int __cdecl XMessageBox(HWND hwnd,
LPCTSTR lpszMessage,
LPCTSTR lpszCaption = NULL,
UINT nStyle = MB_OK | MB_ICONEXCLAMATION,
XMSGBOXPARAMS* pXMB = NULL);
Теперь перейдите к XMessageBox.cpp
, найдите XMessageBox()
. Вот что вы должны увидеть:
///////////////////////////////////////////////////////////////////////////////
//
// XMessageBox()
//
// The XMessageBox function creates, displays, and operates a message box.
// The message box contains an application-defined message and title, plus
// any combination of predefined icons, push buttons, and checkboxes.
//
// For more information see
// http://www.codeproject.com/KB/dialog/xmessagebox.aspx
//
// int XMessageBox(HWND hwnd, // handle of owner window
// LPCTSTR lpszMessage, // address of text in message box
// LPCTSTR lpszCaption, // address of title of message box
// UINT nStyle, // style of message box
// XMSGBOXPARAMS * pXMB) // optional parameters
//
// PARAMETERS
//
// hwnd - Identifies the owner window of the message box to be
// created. If this parameter is NULL, the message box
// has no owner window.
//
// lpszMessage - Pointer to a null-terminated string containing the
// message to be displayed.
//
// lpszCaption - Pointer to a null-terminated string used for the
// dialog box title. If this parameter is NULL, the
// default title Error is used.
//
// nStyle - Specifies a set of bit flags that determine the
// contents and behavior of the dialog box. This
// parameter can be a combination of flags from the
// following groups of flags.
//
// pXMB - Pointer to optional parameters. The parameters
// struct XMSGBOXPARAMS is defined in XMessageBox.h.
//
///////////////////////////////////////////////////////////////////////////////
int XMessageBox(HWND hwnd,
LPCTSTR lpszMessage,
LPCTSTR lpszCaption /*= NULL*/,
UINT nStyle /*= MB_OK | MB_ICONEXCLAMATION*/,
XMSGBOXPARAMS* pXMB /*= NULL*/)
{
...
}
Замените эту функцию на:
extern "C" __declspec(dllexport) int __cdecl XMessageBox(HWND hwnd,
LPCTSTR lpszMessage,
LPCTSTR lpszCaption /*= NULL*/,
UINT nStyle /*= MB_OK | MB_ICONEXCLAMATION*/,
XMSGBOXPARAMS* pXMB /*= NULL*/)
{
...
}
Затем замените XHyperLink.cpp
, XMessageBox.cpp
строку #include "StdAfx.h"
или #include "stdafx.h"
на #include "pch.h"
:
Теперь нажмите Ctrl + Shift + B, чтобы построить проект. После этого вы получите это <Your Solution Directory>\x64\Release
(обратите внимание, что x64
и Release
будут зависеть от конфигурации вашей сборки):
Вы получите свою DLL следующим образом:
Теперь давайте проверим DLL, чтобы убедиться, что функция экспортирована. Я буду использовать PE Viewer
в комплекте с System Informer
, вы можете использовать другой инструмент, например CFF Explorer
. Откройте свою DLL внутри инспектора, и вы получите это:
Нажмите на вкладку Exports
:
и вы увидите что-то вроде:
Если нет, проверьте, есть ли у вас __declspec(dllexport)
и extern "C"
. Обратите внимание, что extern "C"
требуется, чтобы сообщить компьютеру, что эту функцию не следует украшать. Если нет, вы увидите, что Undecorated name
имеет значение. В этом случае убедитесь, что вы уже поставили extern "C"
перед __declspec(dllexport)
. Перестройте проект заново после внесения изменений.
Убедившись, что ваша функция DLL была экспортирована правильно, вернитесь в Visual Studio, а затем в том же решении создайте новую C++/CLI DLL Project
.
Если вы не знаете, что такое C++/CLI DLL Project
:
C++/CLI — это вариант языка программирования C++, модифицированный для Общеязыковая инфраструктура. Он был частью Visual Studio 2005. и более поздних версий, а также обеспечивает совместимость с другими языками .NET. например, С#. Microsoft создала C++/CLI для замены управляемых расширений для С++. В декабре 2005 года Ecma International опубликовала C++/CLI. спецификации соответствуют стандарту ECMA-372.
Теперь в Visual Studio создайте C++/CLI DLL Project
, дайте им любое имя и нажмите Create
:
В зависимости от того, написан ли ваш проект на .NET или .NET Framework, вы должны выбрать его правильно. Мое приложение написано на .NET, поэтому я выберу .NET.
Теперь вернемся к декларации XMessageBox()
:
extern "C" __declspec(dllexport) int __cdecl XMessageBox(HWND hwnd,
LPCTSTR lpszMessage,
LPCTSTR lpszCaption = NULL,
UINT nStyle = MB_OK | MB_ICONEXCLAMATION,
XMSGBOXPARAMS* pXMB = NULL);
Мы видим необходимые параметры XMSGBOXPARAMS
. Так где XMSGBOXPARAMS
? Они определены в одном файле XMessageBox.h
:
struct XMSGBOXPARAMS
{
XMSGBOXPARAMS()
{
nTimeoutSeconds = 0;
nDisabledSeconds = 0;
hInstanceIcon = NULL;
hInstanceStrings = NULL;
lpReportFunc = NULL;
dwReportUserData = 0;
nIdHelp = 0;
nIdIcon = 0;
nIdCustomButtons = 0;
nIdReportButtonCaption = 0;
x = 0;
y = 0;
dwOptions = 0;
lpszModule = NULL;
nLine = 0;
bUseUserDefinedButtonCaptions = FALSE; //+++1.5
crText = CLR_INVALID; //+++1.8
crBackground = CLR_INVALID; //+++1.8
memset(szIcon, 0, sizeof(szIcon));
memset(szCustomButtons, 0, sizeof(szCustomButtons));
memset(szReportButtonCaption, 0, sizeof(szReportButtonCaption));
memset(szCompanyName, 0, sizeof(szCompanyName));
memset(&UserDefinedButtonCaptions, 0, sizeof(UserDefinedButtonCaptions)); //+++1.5
}
UINT nIdHelp; // help context ID for message;
// 0 indicates the application’s
// default Help context will
// be used
int nTimeoutSeconds; // number of seconds before the
// default button will be selected
int nDisabledSeconds; // number of seconds that all the
// buttons will be disabled - after
// nDisabledSeconds, all buttons
// will be enabled
int x, y; // initial x,y screen coordinates
enum // these are bit flags for dwOptions
{
None = 0x0000,
RightJustifyButtons = 0x0001, // causes buttons to be right-justified
VistaStyle = 0x0002, // setting this option bit will cause the
// message background to be painted with
// the current window color (typically
// white), and the buttons to be
// right-justified. +++1.8
Narrow = 0x0004 // uses a narrow width for message box -
// SM_CXSCREEN / 3
};
DWORD dwOptions; // options flags
HINSTANCE hInstanceStrings; // if specified, will be used to
// load strings
HINSTANCE hInstanceIcon; // if specified, will be used to
// load custom icon
UINT nIdIcon; // custom icon resource id
TCHAR szIcon[MAX_PATH]; // custom icon name
UINT nIdCustomButtons; // custom buttons resource id
TCHAR szCustomButtons[MAX_PATH]; // custom buttons string
UINT nIdReportButtonCaption; // report button resource id
TCHAR szReportButtonCaption[MAX_PATH];// report button string
TCHAR szCompanyName[MAX_PATH]; // used when saving checkbox state in registry
LPCTSTR lpszModule; // module name (for saving DoNotAsk state)
int nLine; // line number (for saving DoNotAsk state)
DWORD dwReportUserData; // data sent to report callback function
XMESSAGEBOX_REPORT_FUNCTION lpReportFunc; // report function
COLORREF crText; // message text color +++1.8
COLORREF crBackground; // message background color +++1.8
//-[UK
// For not loading from resource but passing directly,
// Use the following code.
struct CUserDefinedButtonCaptions
{
TCHAR szAbort[MAX_PATH];
TCHAR szCancel[MAX_PATH];
TCHAR szContinue[MAX_PATH];
TCHAR szDoNotAskAgain[MAX_PATH];
TCHAR szDoNotTellAgain[MAX_PATH];
TCHAR szDoNotShowAgain[MAX_PATH];
TCHAR szHelp[MAX_PATH];
TCHAR szIgnore[MAX_PATH];
TCHAR szIgnoreAll[MAX_PATH];
TCHAR szNo[MAX_PATH];
TCHAR szNoToAll[MAX_PATH];
TCHAR szOK[MAX_PATH];
TCHAR szReport[MAX_PATH];
TCHAR szRetry[MAX_PATH];
TCHAR szSkip[MAX_PATH];
TCHAR szSkipAll[MAX_PATH];
TCHAR szTryAgain[MAX_PATH];
TCHAR szYes[MAX_PATH];
TCHAR szYesToAll[MAX_PATH];
};
BOOL bUseUserDefinedButtonCaptions; //+++1.5
CUserDefinedButtonCaptions UserDefinedButtonCaptions; //+++1.5
//-]UK
};
Итак, нам нужна структура XMSGBOXPARAMS
, чтобы наша DLL-обертка работала. Самый простой способ — просто скопировать XMessageBox.h
в каталог нашего проекта, добавить в раздел «Файлы заголовков» и закомментировать функцию, которую нам не нужно использовать в нашей оболочке, а также предотвратить ошибки.
Сделайте то же самое, чтобы скопировать XMessageBox.h
в проект и добавить в файлы заголовков, вот конечный результат:
Теперь перейдите к XMessageBox.h
в своем проекте-оболочке и прокомментируйте этот раздел:
int XMessageBox(HWND hwnd,
LPCTSTR lpszMessage,
LPCTSTR lpszCaption = NULL,
UINT nStyle = MB_OK | MB_ICONEXCLAMATION,
XMSGBOXPARAMS* pXMB = NULL);
DWORD XMessageBoxGetCheckBox(LPCTSTR lpszCompanyName, LPCTSTR lpszModule, int nLine);
DWORD XMessageBoxGetCheckBox(XMSGBOXPARAMS& xmb);
Ваш файл теперь должен выглядеть так:
Теперь создайте новый файл C++ в разделе «Исходные файлы». Дайте ему любое имя, которое вам нравится. Затем используйте этот код:
#pragma once
#include "XMessageBox.h"
#using <System.dll>
#using <System.Drawing.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Drawing;
namespace XMessageBoxWrapper {
public ref class XMessageBoxParams {
private:
XMSGBOXPARAMS* m_pNativeParams;
public:
XMessageBoxParams() {
m_pNativeParams = new XMSGBOXPARAMS();
}
~XMessageBoxParams() {
this->!XMessageBoxParams();
}
!XMessageBoxParams() {
if (m_pNativeParams != nullptr) {
delete m_pNativeParams;
m_pNativeParams = NULL;
}
}
// Properties
property UInt32 IdHelp {
UInt32 get() {
return m_pNativeParams->nIdHelp;
}
void set(UInt32 value) {
m_pNativeParams->nIdHelp = value;
}
}
property Int32 TimeoutSeconds {
Int32 get() {
return m_pNativeParams->nTimeoutSeconds;
}
void set(Int32 value) {
m_pNativeParams->nTimeoutSeconds = value;
}
}
property Int32 DisableSeconds {
Int32 get() {
return m_pNativeParams->nDisabledSeconds;
}
void set(Int32 value) {
m_pNativeParams->nDisabledSeconds = value;
}
}
property System::Drawing::Color TextColor {
System::Drawing::Color get() {
return System::Drawing::Color::FromArgb(
GetRValue(m_pNativeParams->crText),
GetGValue(m_pNativeParams->crText),
GetBValue(m_pNativeParams->crText)
);
}
void set(System::Drawing::Color value) {
m_pNativeParams->crText = RGB(value.R, value.G, value.B);
}
}
property System::Drawing::Color BackgroundColor {
System::Drawing::Color get() {
return System::Drawing::Color::FromArgb(
GetRValue(m_pNativeParams->crBackground),
GetGValue(m_pNativeParams->crBackground),
GetBValue(m_pNativeParams->crBackground)
);
}
void set(System::Drawing::Color value) {
m_pNativeParams->crBackground = RGB(value.R, value.G, value.B);
}
}
XMSGBOXPARAMS* GetNativePointer() {
return m_pNativeParams;
}
};
public ref class XMessageBoxWrapper {
public:
[DllImport("Win32MFC.dll", CharSet = CharSet::Unicode, CallingConvention = CallingConvention::Cdecl, ExactSpelling = true)]
static int XMessageBox(IntPtr hWnd,
[MarshalAs(UnmanagedType::LPWStr)] String^ lpszMessage,
[MarshalAs(UnmanagedType::LPWStr)] String^ lpszCaption,
unsigned int nStyle,
IntPtr pXMB);
// Overloads
static int XMessageBox(IntPtr hWnd, String^ lpszMessage) {
return XMessageBox(hWnd, lpszMessage, nullptr, 0x00000030, IntPtr::Zero);
}
static int XMessageBox(IntPtr hWnd, String^ lpszMessage, String^ lpszCaption) {
return XMessageBox(hWnd, lpszMessage, lpszCaption, 0x00000030, IntPtr::Zero);
}
static int XMessageBox(IntPtr hWnd, String^ lpszMessage, String^ lpszCaption, unsigned int nStyle) {
return XMessageBox(hWnd, lpszMessage, lpszCaption, nStyle, IntPtr::Zero);
}
static int XMessageBox(IntPtr hWnd, String^ lpszMessage, String^ lpszCaption, unsigned int nStyle, XMessageBoxParams^ params) {
if (params == nullptr) {
return XMessageBox(hWnd, lpszMessage, lpszCaption, nStyle, IntPtr::Zero);
}
else {
return XMessageBox(hWnd, lpszMessage, lpszCaption, nStyle, IntPtr(params->GetNativePointer()));
}
}
};
}
Теперь создайте свой проект. Теперь вы должны увидеть две библиотеки DLL, например:
Если вам интересно, что такое ijwhost.dll
:
Для поддержки библиотек C++/CLI в .NET Core был создан
ijwhost
как shim для поиска и загрузки среды выполнения. Все библиотеки C++/CLI связан с этой прокладкой, так чтоijwhost.dll
находится/загружается, когда Библиотека C++/CLI загружена.
Теперь мы почти закончили! Вернитесь к своему проекту C#, щелкните правой кнопкой мыши свой проект (у него есть логотип C#), затем выберите Add > Project reference...
и вы увидите свою C++/CLI DLL
обертку:
Проверьте это, затем выберите «ОК», чтобы добавить ссылку.
Теперь мы можем протестировать нашу программу:
Программа.cs
using OSVersionExtension;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using XMessageBoxWrapper;
namespace Education
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool SetProcessDPIAware();
static void Main()
{
// With custom parameters
XMessageBoxParams xParams = new XMessageBoxParams();
xParams.IdHelp = 12345;
xParams.TimeoutSeconds = 10;
xParams.DisableSeconds = 5;
xParams.TextColor = Color.Red;
xParams.BackgroundColor = Color.Blue;
XMessageBoxWrapper.XMessageBoxWrapper.XMessageBox(IntPtr.Zero, "Hello, world!", "Custom Caption", 0x00000030, xParams);
ApplicationConfiguration.Initialize();
//HookMessageBoxW();
//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));
}
}
}
}
Создайте свой проект, но не запускайте приложение
Скопируйте Win32MFC.dll
в свой каталог, содержащий ваше приложение C# в EXE-файле. После этого ваш каталог будет выглядеть так:
Вот результат, который вы должны увидеть:
Удачи!
Добро пожаловать в Stack Overflow. В dllmain.cpp с вашей функцией
MessageBoxSubclassProc
есть строкаHBRUSH hBrush = CreateSolidBrush(bgColor);
, но вы нигде там не используетеhBrush
. Возможно, что еще более важно, для значенийuMsg
WM_CTLCOLORDLG
,WM_CTLCOLORSTATIC
иWM_CTLCOLORBTN
есть строкаDeleteObject(hBrush); // Make sure to delete the brush after use
, за которой следуетreturn (LRESULT)hBrush;
, т. е. вы возвращаете удаленное значениеHBRUSH
! Я не проверял, что с этим делает процедура Windows, но, похоже, это неправильно.