Попытка подключить MessageBox в C++ DLL и использовать их в приложении C#

Я использую 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)

Добро пожаловать в Stack Overflow. В dllmain.cpp с вашей функцией MessageBoxSubclassProc есть строка HBRUSH hBrush = CreateSolidBrush(bgColor);, но вы нигде там не используете hBrush. Возможно, что еще более важно, для значений uMsgWM_CTLCOLORDLG, WM_CTLCOLORSTATIC и WM_CTLCOLORBTN есть строка DeleteObject(hBrush); // Make sure to delete the brush after use, за которой следует return (LRESULT)hBrush;, т. е. вы возвращаете удаленное значение HBRUSH! Я не проверял, что с этим делает процедура Windows, но, похоже, это неправильно.

John Omielan 16.08.2024 07:13

@JohnOmielan спасибо за ваш ответ, я удалил DeleteObject(), но, к сожалению, все еще не работает. Я потратил несколько часов, чтобы выяснить, что происходит, но безуспешно… Думаю, мне нужно вернуться к документации Windows, но, к сожалению, я не смог найти там решения….

CyberDay 16.08.2024 09:52

Родной отладчик должен что-то показать. Возможно, необходимо изменить некоторые параметры, например. обработка исключений, особенно Win32 (Отладка->Windows->Исключения). Я проверю, что все указатели содержат ожидаемые значения (oldButtonProc, GWLP_USERDATA, GWLP_WNDPROC).

Daniel Sęk 16.08.2024 12:39

@DanielSęk, спасибо, теперь я вижу ошибку в строке if (lpcs->lpszClass && wcscmp(lpcs->lpszClass, L"#32770") == 0) { с ошибкой: Исключение, созданное по адресу 0x00007FFD2766F7FA (ucrtbased.dll) в Education.exe: 0xC0000005: Местоположение чтения нарушения прав доступа 0x0000000000008002.

CyberDay 16.08.2024 16:12

Рассмотрите возможность использования менее хрупкого способа управления подклассами. Вам также необходимо прекратить использование GWLP_USERDATA; это не твое.

IInspectable 16.08.2024 16:25

Черт возьми, как эта строка могла привести к нарушению доступа по адресу 0x00008002? Может быть, lpszClass — это 0x8002? Это допустимое значение для lpszClass (но не wcscmp): Согласно документации, lpszClass является «указателем на строку с нулевым завершением или атом, который определяет имя класса нового окна».

Raymond Chen 16.08.2024 16:31

32770 == 0x8002

Daniel Sęk 16.08.2024 16:44

Используйте макрос IS_INTRESOURCE, чтобы различать имя класса и атом. Убедитесь, что вы понимаете, что документация лжет вам, когда говорится: «Возвращаемое значение: нет».

IInspectable 16.08.2024 17:34

Вот что меня смущает. Сейчас я думаю о том, что, возможно, защита от исправлений ядра или антивирус не позволяют моему приложению перехватить мой MessageBox. Но я работаю в пользовательской среде, а не в ядре, и даже если я отключу антивирус, запущу от имени администратора или NT AUTHORITY\SYSTEM, ошибка все равно возникнет. Кроме того, я подключаю MessageBox только к текущему процессу, он не перехватывает всю систему. Кроме того, XMessageBox — декомпилированная версия API MessageBox, относящаяся к Windows XP, она до сих пор работает в Windows 11, так что, возможно, мой код делает что-то глупое.

CyberDay 18.08.2024 17:12
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
9
92
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

После включения собственного кода отладки я вижу ошибку:

Исключение, возникающее по адресу 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 для использования недокументированной функции, но я думаю, что это тоже нехорошо

CyberDay 18.08.2024 17:29
Ответ принят как подходящий

После 3 дней отладки (включая опробование всех методов, которые я придумал: проверка границ, проверка нулевого указателя, проверка ссылок...), но все еще застрял на нарушении прав доступа, я нашел решение, которое кажется более совершенным, чем один хук. Однако, чтобы заставить его работать, потребуется немало манипуляций, но я думаю, оно того стоит, особенно когда оно работает идеально.

Шаги будут довольно длинными, но я постараюсь быть максимально подробным. Короче говоря, я использовал XMessageBox, реверс-инжиниринг API Windows MessageBox, который восходит к эпохе Windows XP и Windows Vista и работает до сих пор. К счастью, Microsoft, вероятно, сохранила API MessageBox нетронутым и по сей день.

Я надеюсь, что это поможет людям, у которых такая же проблема, как и у меня. Если вы все еще просите решение, подключите MessageBox API: Извините, вы больше не можете этого сделать.

Вот подробности:

  1. Во-первых, нам нужен XMessageBox. Быстрый поиск в Google выдаст результаты. Затем перейдите на официальную страницу проекта на CodeProject и загрузите его. После скачивания разархивируйте файл. После распаковки мы получим:

  1. Перейдите в папку vs6\Release и запустите XMsgBoxTest.exe. Это будет то, с чего мы начнем

  1. Здесь вы можете попробовать разные типы MessageBox. По моему требованию мне нужно изменить цвет фона и цвет текста. Нажимаем 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:

проблема с github

Для поддержки библиотек 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-файле. После этого ваш каталог будет выглядеть так:

Вот результат, который вы должны увидеть:

Удачи!

Другие вопросы по теме