Занимался я недавно портированием одного старого win32 MFC MDI приложения, до сих пор разрабатываемого с использованием Microsoft Visual Studio 6.0 (среда разработки 1998 года выпуска), на Linux. При чем необходимо было, чтобы под Linux собирался нативный бинарник в формате ELF из исходников. Взгляд упал на проект Wine, позволяющий запускать бинарники Windows (формат PE - Portable Executable). Один из компонентов Wine - библиотека Winelib - является прослойкой (враппером) между вызовами win32 и вызовами библиотек Linux. С помощью Winelib можно под Linux собирать win32 приложения из исходников. В составе Winelib есть десятки врапперов стандартных библиотек Windows. Но вот незадача. Враппера для MFC (Microsoft Foundation Classes) нет. В итоге прослойку между классами MFC и вызовами win32, пригодную для компиляции с использованием Winelib, пришлось готовить самому. Далее будет описан путь к решению задачи, а также компромиссы, на которые пришлось пойти по ходу реализации.

Небольшая историческая справка. Winelib это не первая попытка обеспечить компиляцию win32 приложений под *nix-системами. Еще ранее, для Solaris Unix были созданы несколько прослоек, реализующих стандартные библиотеки Windows, для обеспечения запуска старых версий Internet Exploler.

Так вот, необходимо было обеспечить наличие MFC под Winelib. Стоит отметить, что все библиотеки прослойки из состава Winelib реализуют функционал, аналогичный библиотекам Windows, но при этом написаны с нуля сообществом Wine. Исходник же MFC включен в состав установленного Visual Studio. Поиск в интернете в части того, как собрать MFC с использованием Winelib, дал следующие результаты.

В общем случае понятно, что нужно выставить определенные макроопределения для исходников MFC, чтобы не включать поддержку прям всего. Выставив эти макросы, я принялся компилировать MFC под линукс. Взял я MFC 4.2 из состава Visual Studio 6.0, поскольку портируемое приложение использует именно эту среду разработки и эту версию MFC. К слову, компиляция осуществляется враппером над gcc с названием winegcc. А создание make-файла осуществляется perl-скриптом winemaker.

Сделав более 30 правок в самых разных местах исходника MFC 4.2, оно наконец скомпилировалось. Добавив созданное в Visual Studio 6.0 минимальное приложение MFC я запустил полученный бинарник. И получил вылет сразу после запуска. Оказалось, что конструкторы глобальных объектов, в случае Winelib, должны вызываться определенным образом.

Собранное таким образом вылетающее приложение было получено из полных исходников MFC, ограниченных несколькими макросами. Теоретически, много какие места могли конфликтовать друг с другом в случай Winelib сборки. Это увеличивало количество точек отказа и время потенциальной отладки. Поэтому далее я решил действовать совсем по другому - от простого к сложному. Создав под MSVS 6.0 проект нового win32 (не MFC) приложения, я добавил к нему один единственный файл mfc.cpp, в который стал копировать определения и реализацию того функционала MFC, чтобы получить минимальное работающее диалоговое MFC приложение. Т.е. с точки зрения среды разработки это было win32 приложение, в котором отдельным файлом реализован минимальный функционал MFC, и которое не линкуется статически или динамически с бинарными библиотеками MFC, идущими в комплекте со средой разработки.

Когда подобное минимальное приложение стало компилироваться и работать под Windows, я приступил к компиляции результирующего mfc.cpp под Linux с использованием winegcc и Winelib. В этот раз, для обеспечения корректной инициализации конструкторов глобальных объектов, все приложение + реализация MFC были вынесены в отдельный подгружаемый модуль .dll.so. Отловив и пропатчив несколько исключений в исходнике MFC mfc.cpp с помощью gdb, под Linux наконец появилось окно MFC приложения, скомпилированного из исходников в нативный ELF бинарник.

Окно не отображало русский язык, но это решилось добавлением в систему локали ru_RU.cp1251 и вызовом приложения с префиксом "LC_ALL=ru_RU.cp1251". Изначальное портируемое приложение - Multibyte Win-1251, т.е. не Unicode. Поэтому мой mfc.cpp заточен только под MB Win1251 (хотя, в общем смысле, MFC 4.2 поддерживает Unicode, но я этого не тестировал).

Далее, в Windows, я вместо минимального диалогового приложения, подключил к сборке с mfc.cpp полный исходник портируемого MDI приложения. Пришлось к mfc.cpp добавить много реализации MFC из исходников. А также сделать несколько workarounds. Везде, где есть отличия от оригинальных исходников MFC, идут обрамления _AFX_REDUCED_SOURCEPRINT, _AFX_PLATFORM_LINUX, _AFX_TARGET_ARM32.

Далее приведен список workarounds относительно оригинального исходника MFC 4.2 из состава Visual Studio 6.0, которые понадобились для обеспечения запуска MFC под вкомпилированной сборке MFC под Windows (_AFX_REDUCED_SOURCEPRINT), под Linux с использованием Winelib (_AFX_PLATFORM_LINUX) и под не х86-архитектурами (_AFX_TARGET_ARM32). Возможно все, или большинство из этих workarounds, можно реализовать более правильно и корректно, нежели они реализованы, но в рамках решаемой задачи тратить время на эти исследования не было возможности и необходимости. Итоговое портированное приложение работает стабильно, в части своего непосредственного функционала.

  • При определенных условиях не отрисовывает и не уничтожает Tootip.

  • Не работает с ярлыками на приложение.

  • В некоторых случаях не производит парсинг параметров командной строки.

  • Использует старый способ для перемещения/переименования файлов.

  • В некоторых случаях отключено автоматическое преобразование строк, из-за чего проект привязан к Multibyte ANSI.

  • Не отслеживаются некоторые события изменения состава подключенных принтеров.

  • Не освобождается память в одном из деструкторов, вызываемых при завершении приложения.

  • Процесс закрывается принудительно после закрытия последнего по счету CFrameWnd.

  • Внедрено обнаружение некорректных параметров при работе с ресурсами диалога в CWnd::ExecuteDlgInit().

  • При некоторых условиях добавляются дополнительные пустые глобальные __argc и __argv.

Изначальное портируемое приложение также имело плагин на Qt. Поэтому приложенный архив имеет также проект небольшой библиотеки на Qt, которая подключается к приложению Winelib с MFC. Замечу, что приложенный архив включает демонстрационный пример пустого MDI проекта, down-портированного из MSVS 2010 на мой mfc.cpp.

Архив: https://disk.yandex.ru/d/fUHhfWKqkZbojA

ЗЫ: Выложенные материалы ни в коем случае не позиционируются как панацея или единственно верный путь. Они могут лишь стать отправной точкой в случае, если необходимо решить задачу портирования MFC приложения на Linux. В проекте решено множество подводных камней, связанных с компиляцией и линковкой. Но, например, приложенный Makefile не лишен недостатков (например он каждый раз перекомпилирует весь проект заново, даже если не все исходные файлы изменились). Использовать проект для переноса своей MFC программы на Linux имеет смысл только, если у вас уже есть достаточный опыт сборки проектов под Linux.

ЗЗЫ: Приведенный mfc.cpp содержит не весь MFC 4.2 целиком. Если ваше приложение использует какие-то другие классы и методы MFC и при линковке ругается на GetRuntimeClass(), GetMessageMap(), messageMap, classC, то нужно смотреть в сторону IMPLEMENT_DYNAMIC(), IMPLEMENT_DYNCREATE(), IMPLEMENT_FIXED_ALLOC(), BEGIN_MESSAGE_MAP(), производя полнотекстный поиск необоходимых классов в исходниках MFC из состава Visual Studio.

Комментарии (1)


  1. berez
    29.07.2022 00:12
    -2

    Запуск MFC-приложения на не-x86 архитектуре нативно

    Мне почему-то всегда казалось, что wine работает только на x86-архитектуре (и на x64).
    Вы не путаете операционную систему с архитектурой процессора?