Реализация оконных сообщений в Win3mu — Часть 2 Семантика сообщений
Win3mu — это эмулятор Windows 3, который позволяет запускать старые игры Windows в современных версиях Windows. Об обосновании этого проекта можно прочитать здесь.
Этот пост продолжает обсуждение того, как обрабатываются сообщения Windows, на этот раз рассматривая семантику сообщений — то есть различные типы сообщений и то, как они отображаются между эмулируемой и собственной средами.
Резюме
В предыдущем посте я упомянул следующие два метода, у которых есть грязная работа по переводу 16-битного сообщения Windows в 32/64-битное сообщение и наоборот.
Потребовалось 3 или 4 разных попытки дизайна, прежде чем я, наконец, остановился на том, что меня удовлетворило. В этом посте описаны возникшие проблемы и окончательное решение.
Карта сообщений
В основе обработки сообщений лежит класс MessageMap:
Его работа заключается в поиске номера сообщения для конкретного окна и возвращении экземпляра MessageSemantics.Base — абстрактного класса (показанного выше), из которого выводятся классы для определения конкретных видов преобразования сообщений.
Почтовые и вызываемые сообщения
Windows поддерживает два механизма доставки сообщений — опубликованные сообщения и отправленные сообщения.
- Опубликованные сообщения помещаются в очередь сообщений приложения и извлекаются приложением при обработке цикла сообщений.
- Отправленные сообщения передаются непосредственно оконной процедуре с помощью функции Windows SendMessage.
В Win3mu есть два основных семантических класса сообщений: Отправляемые и Вызываемые, которые примерно соотносятся с опубликованными и отправленными сообщениями.
- Публикуемые сообщения имеют автономные параметры. То есть ни один из параметров не является указателем.
- Вызываемые сообщения могут иметь параметры-указатели, но могут быть только отправлены, но не опубликованы.
Обратите внимание, что сообщение Postable можно отправить, а сообщение Callable нельзя — вот почему я говорю, что они приблизительно коррелируют.
Семантика публикуемых сообщений
Вот класс семантики Postable:
Ниже я расскажу о механизме обхода. А пока обратите внимание, что каждый метод (To32
и To16
) просто берет сообщение и преобразует его.
В качестве примера, вот класс семантики сообщения hwnd_copy
. Он обрабатывает сообщения, в которых wParam является дескриптором окна, а lParam необходимо передать (то есть «скопировать»):
Это довольно прямолинейно: wParam отображается с использованием карты дескриптора окна, а lParam приводится к соседнему типу.
Семантика вызываемых сообщений
Вызываемые сообщения более сложны:
Некоторые моменты, на которые следует обратить внимание:
- Параметры
hook
иdlgproc
указывают, является ли это вызовом процедуры ловушки или вызываемой процедурой диалогового окна. Семантика некоторых сообщений в этих случаях ведет себя несколько иначе. - Существует функция обратного вызова, которую реализация должна вызывать после преобразования сообщения.
- При возврате из функции обратного вызова класс семантики сообщения может выполнять обратное преобразование очистки и т. д.
- Есть возвращаемое значение.
В качестве примера, вот класс copy_string
, который копирует параметр wParam и передает указатель на строку в lParam:
Как вы можете видеть, это более сложно — строки должны быть маршалированы между двумя средами, и часто после вызова сообщения происходит очистка.
Обход сообщений
В современных версиях Windows есть довольно много новых сообщений, которых просто не было в Windows 3. Хотя эти сообщения не нужно преобразовывать в 16-битные, их нужно передавать любой базовой оконной процедуре — часто DefWindowProc. .
Чтобы облегчить это, Win3mu использует «обходные» сообщения. Если класс семантики сообщения возвращает true для ShouldBypass, то сообщение обрабатывается таким образом, что 16-разрядная программа никогда его не увидит.
Обход сообщений осуществляется одним из двух способов. Для сообщений в очереди сообщений пропущенные сообщения просто не возвращаются в 16-битную программу через PeekMessage/GetMessage — они передаются напрямую в DispatchMessage
Для сообщений, поступающих в оконную процедуру:
- Параметры сообщения упаковываются во временную структуру и получают «Id».
- В 16-битную оконную процедуру, передающую идентификатор, отправляется специальное сообщение об обходе.
- 16-разрядная оконная процедура проигнорирует сообщение (поскольку оно неизвестно 16-разрядным программам) и передаст его оконной процедуре по умолчанию или исходной оконной процедуре подкласса окна.
- Win3mu получает специальное обходное сообщение с другой стороны, распаковывает временно упакованные параметры, ища идентификатор, и продолжает обработку.
Это кажется большой проблемой, чтобы доставить сообщение к его правильному конечному адресату, но если вы принимаете во внимание подклассы и суперклассы окна, это единственный способ гарантировать, что правильная базовая оконная процедура получит сообщение.
Упаковать и опубликовать сообщения
Программа Windows может определять свои собственные пользовательские сообщения и публиковать или отправлять их в свои собственные окна. Это представляет проблему для Win3mu, поскольку он не может узнать, является ли сообщение доступным для отправки или вызова, а также как должны обрабатываться параметры.
Для отправляемых сообщений это можно сделать довольно легко — достаточно, чтобы реализация SendMessage в Win3mu передала параметры непосредственно 16-битной оконной процедуре.
Для отправленных сообщений это немного сложнее, потому что сообщение должно попасть в очередь сообщений (чтобы получить правильное поведение отложенной доставки), но Win3mu нужно знать, что делать с сообщением, когда оно извлечено из очереди сообщений.
Win3mu использует для этого другое специальное сообщение. WM_PACKANDPOST — это просто 16-битное сообщение, переупакованное следующим образом:
- сообщение = WM_PACKANDPOST;
- wParam hi-word = исходный 16-битный номер сообщения
- wParam lo-word = исходное 16-битное значение wParam
- lParam = исходное 16-битное значение lParam
Теперь всякий раз, когда Win3mu видит сообщение WM_PACKANDPOST, он знает, что оно создано 16-битной программой, и никакой специальной обработки параметров не требуется — он может просто распаковать сообщение и передать его обратно 16-битной программе.
Использование семантики сообщений
Теперь у нас есть все необходимое для преобразования оконного сообщения из одной среды в другую. Я удалил некоторые детали, но это метод CallWndProc16From32. (Эквивалентный метод 32-из-16 не показан, но очень похож).
Реализация MessageMap
Я упомянул, что работа MessageMap заключается в том, чтобы определить правильную семантику сообщения для конкретного оконного сообщения.
Вы могли бы подумать, что его работа будет простой, но, как и все, что связано с обменом сообщениями, это не так. Конкретный номер сообщения может означать разные вещи в зависимости от окна, в которое оно отправляется, и некоторые номера сообщений различаются между 16-битными и 32/64-битными.
- Сообщения в диапазоне 0x0000–0x03FF никогда не меняют своего значения. Это системные сообщения, и их семантику можно просто посмотреть на карте.
- Сообщения в диапазоне 0x0400–0x8000 являются сообщениями пользователя и требуют специальной обработки (см. ниже).
- Сообщения в диапазоне 0x8000–0xBFFF являются сообщениями приложений, они упаковываются и публикуются.
- Сообщения в диапазоне 0xC000–0xFFFF являются зарегистрированными сообщениями, и в зависимости от того, зарегистрировала ли 16-битная программа соответствующее оконное сообщение, сообщение будет либо упаковано и отправлено, либо пропущено.
Сообщения в диапазоне 0x400 представляют собой самую большую проблему, потому что именно здесь живут стандартные управляющие сообщения. например: сообщения в списки, поля со списком и т. д.…
Для этих сообщений MessageMap необходимо проверить целевое окно, выяснить, что это за элемент управления, а затем выбрать подходящую семантику сообщения на основе типа окна.
Все это заканчивается набором объявлений карты сообщений, которые выглядят следующим образом:
Класс MessageMap использует всю эту информацию для:
- Выберите подходящий класс семантики сообщения
- При необходимости преобразуйте номер сообщения (например:
Win32.LB_ADDSTRING
отличается отWin16.LB_ADDSTRING
)
Определение класса окна
Как только что упоминалось, MessageMap иногда необходимо знать класс окна, чтобы выбрать подходящую семантику сообщения.
Проблема здесь в суперклассах. Это зарегистрированные классы окон, которые наследуют поведение встроенного класса окон и поэтому нуждаются в той же семантике сообщений, но имеют другое имя класса.
Например: если программа суперклассирует список, даже если у него другое имя класса, ей все равно необходимо использовать семантику сообщений класса списка.
Win3mu использует несколько различных методов для решения этой проблемы, но в основном рассматривает окончательную процедуру окна, которую доставляет сообщение WM_NCCREATE, чтобы связать окно со стандартным классом окна.
Реализация семантики сообщений
Последней частью всего этого являются фактические классы семантики сообщений. О них особо нечего сказать, за исключением того, что их довольно много. Некоторые из них имеют общее назначение (например, copy_string
и hwnd_copy
, описанные выше), а другие относятся к конкретным оконным сообщениям:
Вывод
Как и предполагалось с самого начала, обработка сообщений должна была стать одной из самых сложных частей работы Win3mu. Теперь вы можете понять, почему, и это даже больше, чем то, что я описал. Обработка WM_CREATE и WM_NCCREATE сама по себе ужасна, но я думаю, что пока достаточно об обмене сообщениями.
Обновлять
Вы, наверное, заметили, что в последнее время я довольно много говорю о Win3mu в Твиттере. Это в основном потому, что я был сосредоточен на переносе Cantabile (моего музыкального программного обеспечения) на OS X.
Однако теперь я также совершенно уверен, что собираюсь выпустить Win3mu как продукт, поэтому я работал над связанными вещами, такими как:
- веб-сайт — см. http://www.win3mu.com,
- установщик,
- произведения искусства и
- это видео:
Если вы хотите попробовать Win3mu или просто хотите поддержать его, рассмотрите возможность регистрации на сайте. Я буду использовать это, чтобы оценить интерес и помочь решить, когда и как выпустить его, поэтому, пожалуйста, рассмотрите возможность поделиться этими ссылками.
А если вы зарегистрировались, то получите ранний доступ…
Привет, я Брэд Робинсон — независимый разработчик программного обеспечения, живущий в Сиднее, Австралия. Я пишу программы для музыкантов и как независимый разработчик полагаюсь на сарафанное радио.
Если вам понравилась эта статья, рассмотрите возможность поделиться ею, нажав «рекомендовать сердечко» ниже или поделившись на Facebook/Twitter. Это небольшой жест, но он имеет большое значение.