Реализация оконных сообщений в 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

Для сообщений, поступающих в оконную процедуру:

  1. Параметры сообщения упаковываются во временную структуру и получают «Id».
  2. В 16-битную оконную процедуру, передающую идентификатор, отправляется специальное сообщение об обходе.
  3. 16-разрядная оконная процедура проигнорирует сообщение (поскольку оно неизвестно 16-разрядным программам) и передаст его оконной процедуре по умолчанию или исходной оконной процедуре подкласса окна.
  4. 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 использует всю эту информацию для:

  1. Выберите подходящий класс семантики сообщения
  2. При необходимости преобразуйте номер сообщения (например: 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. Это небольшой жест, но он имеет большое значение.