to post messages and comments.

← All posts tagged SOM

Собрался наконец положить конец неотсканированности книги «Programming with DirectToSOM C++». Человек, которому давал изначально, так и не сделал, сам тоже так и не сделал, ну а книга оказалась не столь монументальна. Но всё же почему бы и нет, пусть другие оценят немонументальность.

Нашёл того, кто готов подработать на профессиональном сканере. Сканер широкий, специально для книг. Придавливает сразу весь разворот, отдельно плиты для левой и правой страниц на разной высоте. И картинка получается прямая, крутить страницы не надо. На простом A4 сканере так хорошо не будет. Но OCR надо всё же будет самому делать.

На накосе смог открыть, наконец, образы Copland, которые не кололись 7zip’ом. SOM DTK там внутри не обнаружил, а это, например, IDL для SOMObject. Но системные IDL там были в изобилии. Однако, что показалось мне странным, многие из них не содержат описание классов. Там только структуры и обратные вызовы.

Но были и те, что с классами, например, всё, что начинается на HI. Это была интересная наводка. Я вспомнил, что в накосе для более лёгкого портирования была (и сейчас есть, но только для 32х бит) система библиотек Carbon. Теперь, когда в экосистеме классической макос мне известно что-то объектное, кроме OpenDoc, стало логичным посмотреть, а куда это делось в Carbon. В статье на Википедии про Carbon прочитал такое:

HIObject — a completely new object-oriented API which brings to Carbon an OO model for building GUIs. This is available in Mac OS X v10.2 or later, and gives Carbon programmers some of the tools that Cocoa developers have long been familiar with. Starting with Mac OS X v10.2, HIObject is the base class for all GUI elements in Carbon. HIView is supported by Interface Builder, part of Apple's developer tools. Traditionally GUI architectures of this sort have been left to third-party application frameworks to provide. Starting with Mac OS X v10.4, HIObjects are NSObjects and inherit the ability to be serialized into data streams for transport or saving to disk.
Но какой может быть «completely new» в системе библиотек, которые нужны только для портирования? Это осколок былого величия, наскоро сделанная замена SOM. Вместо somBuildClassHIObjectRegisterSubclass, вместо somDataResolveHIObjectDynamicCast и т.п. Кстати, доступ к полям через HIObjectDynamicCast значит, что это нехрупкое ABI. Также, через QuickTime SDK, HIObject попал и на Виндоуз тоже, в собственно плеер, Сафари и айТюнс.

OS/2 2.0 Technical Library: System Object Model Guide and Reference
Бывает забавно производить впечатление на разработчиков Objective-C (которые делают libobjc2 для GNUStep, например) тем, что в SOM нехрупкое ABI появилось в 1992м. Они проверяют, и да, так и есть. То, что они, как им кажется, повторяют за Эппл, гораздо старше, чем они думали. Да и в Эппл, с учётом SOM, получается, что нехрупкое ABI появилось не в 2000х, а на десять лет раньше. В 2000х было просто возвращение к некогда занятым позициям, утраченным с приходом Джобса и переходом на менее совершенный в те годы Objective-C.

Но 1992ой — это появление SOM 2.0, который сильно отличался. Там вместо старого SOM OIDL был принят CORBA IDL с дополнениями, было реализовано множественное наследование (к сожалению, по типу C++, а не Dylan/CLOS), и механизм скрещивания метаклассов по требованию. И большая часть материалов по SOM, что удалось найти, была из 1994-1996 годов. Чем был, а чем не был SOM 1.0, соответственно, оставалось неизвестным, и на всякий случай на 1991й год я не замахивался.

Теперь с обнаружением этого документа можно утверждать, что нехрупкий ABI был в SOM с самого начала, с 1991го года. На странице 13 (1-2) прямо указано, что можно добавлять и методы, и поля, и даже удалять непубличные. А на других страницах можно видеть описание старого синтаксиса OIDL, который был только в SOM 1.0.

Unifying types and classes in Python 2.2
One of the coolest, but perhaps also one of the most unusual features of the new classes is the possibility to write "cooperative" classes. Cooperative classes are written with multiple inheritance in mind, using a pattern that I call a "cooperative super call". This is known in some other multiple-inheritance languages as "call-next-method", and is more powerful than the super call found in single-inheritance languages like Java or Smalltalk. C++ has neither form of super call, relying instead on an explicit mechanism similar to that used in classic Python. (The term "cooperative method" comes from "Putting Metaclasses to Work".)Таким образом, в этом отношении Питон продвинулся вперёд по сравнению с SOM. В реальном SOM множественное наследование было реализовано как в C++, топорно и без всяких порядков вызова методов. Соответственно, никакого call-next-method, а вместо этого указатель на унаследованную реализацию получался единоразово во время инициализации класса, либо потом можно было запросить его ещё раз, указав свой класс и индекс родительского класса. Эмиттеры все эти индексы заворачивали в соответствующие имена родительских классов. При этом во весь вставала проблема ромбовидных иерархий, когда к родительским классам вызовы могут либо не приезжать, либо приезжать несколько раз. Чтоб совсем плохо не было, конструкторы, деструкторы и операции присваивания в SOM пользовались битовыми полями (один класс — один бит), отсекающими повторные вызовы. Кооперативные методы, скорее всего, были только в книге. Metaclass Framework в составе SOM был закрытой библиотекой без IDL. Бетатестеры могли получить их по запросу. Так как всего этого нет, однозначно утверждать, что MRO не было, нельзя, но по крайней мере, в общем доступе кооперативных методов не было. До Питона они были только в книге.

The metaclasses book describes a mechanism whereby a suitable metaclass is automatically created, when necessary, through multiple inheritance from M1 and M2. In Python 2.2, I have chosen a simpler approach which raises an exception if the metaclass constraint is not satisfied; it is up to the programmer to provide a suitable metaclass through the __metaclass__ class variable. However, if one of the base metaclasses satisfies the constraint (including the explicitly given __metaclass__, if any), the first base metaclass found satisfying the constraint will be used as the metaclass.А вот тут, к сожалению, сделан шаг назад к семантике CLOS. В SOM 2.0 это было, и, конечно, это было в книге. Возможно, что (кроме длительной недоступности книги, пока я её не отсканил) это причина, почему метаклассы в Питоне не столь развиты. Ведь с таким геморроем ими резко становится не так удобно пользоваться.

SOM для Copland
Распаковал образы Mac OS 8.0 (кодовое имя Copland), — той, которая, как и Windows 95 должна была обновить ОС Макинтошей до современной, с вытесняющей многозадачностью. Но что-то с ядром у них не заладилось, и пришлось выпускать Mac OS 8.5 на базе старого ядра с кооперативной многозадачностью. Но если в обычной Mac OS был Apple SOM, то несколько лет назад я предположил, что и в Copland он автоматически прошёл. Сегодня проверил. Да, так и есть.

Вообще я ожидал найти средства разработчика и посмотреть IDL, но средств разработки на имеющихся образах не оказалось. Извлёк ядро SOM, посмотрел другие бинарники, видно, что они его подключают. Естественно подразумеваемая компонента операционной системы. Применительно к Copland никто и не заморачивался тем, чтобы про это написать.

Почитал Interfacing with the XPCOM cycle collector, посмотрел красивые картинки и подумал, что хотя в идеале лучше иметь царя в голове, который и в этой голове, и в коде порядок наведёт на радость пользователю, не взбешённому фризами от свопов от сборок мусора, но подобным инструментом можно портировать код, в который трассирующая сборка мусора въелась так, что уже не отдраить. Только ещё небольшое дополнение к этой схеме я бы сделал. В норме далеко не любые объекты в строго типизированной системе вообще способны образовать цикл, а если способны, то не любыми своими полями, и это можно отследить по допустимым типам полей, некисло усекая анализируемый подграф. Эту схему чаще всего ломал бы базовый тип (java.lang.Object в Java), так как он-то действительно нередок как универсальное хранилище значений и потенциально может использоваться для образования циклов, но дженерики в Java 6 уменьшают количество мест, где такой универсальный тип может встретиться.
XPCOM для этого плохо подходит, поскольку на один объект может ссылаться несколько двоично отличающихся указателей, и сборщику циклов приходится приходится делать QueryInterface для каждой встреченной ссылки, чтобы привести их к одному знаменателю. Также в XPCOM любой объект потенциально поддерживает любой интерфейс, и статический анализ (или анализ во время инициализации класса) невозможен. Зато возможен в SOM, если модифицировать эмиттеры. Даже в немодифицированном SOM можно открыть хранилище интерфейсов, и у класса InterfaceDef получить свойство InstanceData в формате CORBA TypeCode с TypeCode_kind = tk_struct, и по ней прочесать поля, другое дело, что хранилище интерфейсов не гарантированно синхронизировано с реальным кодом, в отличие от эмиттера, который бы вшивал всегда актуальное описание в служебный код реализации класса. Для тяжёлых случаев можно-таки реализовать аналог nsICycleCollectionParticipant. Лучше всего при инициализации новых классов строить орграф, находить в нём сильные компоненты связности, схлопывать их в один узел, а оставшийся орграф без контуров сортировать по уровням, так что если при попытке добавления нового класса приходится опустить отсортированные ранее, то при наличии вновь появившегося потенциального цикла это сделать не удастся, и цикл будет инкрементально обнаружен. Сборщик цикла может просто сравнивать идентификатор компоненты сильной связности своего класса с идентификаторами у сначала формальных, потом реальных типов полей, не нужно потом обходить их родительские классы. Это позволит впоследствии максимально сузить круг поиска при сборке цикла, по возможности не давая этой отраве выплеснуться наружу, а если уж выплеснулось, то изолировать токсичные выбросы друг от друга.
Сильно завозить эвристику тоже не хочется, а начинать или не начинать сборку циклов — это уже эвристика. Вместо этого, руководствуясь принципом «сначала сделать, чтоб работало, потом сделать, чтоб работало быстро», можно предложить такой способ переноса с Java, когда вообще каждая операция декремента, не обнулившая счётчик ссылок, вызывает сборщик цикла, ограниченный вычисленной компонентой связности для подозрительного объекта. Я бы не удивился, если такая суровая реализация работала даже быстрее, чем все эти свопы и фризы в хвалёном трассирующем сборщике. А если надо, чтобы работало ещё быстрее, — извольте наводить порядок в голове и коде. Кстати, я посмотрел, в Javolution самый обычный счётчик ссылок, и ничего, несколько проектов им портировано. Выходит, по силам Java-разработчикам навести у себя порядок.

J. Hamilton. A model for implementing an object-oriented design without language extensions
Почитал. Выходит, селекторы должны указывать на смещение внутри таблицы виртуальных методов, а эти таблицы в общем случае должны предусматривать селекторы вообще для любого метода любого класса, а то, что на самом деле там их меньше, — это оптимизация. Этот доклад ссылается на объектные модели Smalltalk и Objective-C, в которых одиночное наследование, при этом ссылка на этот доклад найдена в книге «Programming with DirectToSOM C++», где модель однозначно поддерживает множественное наследование. Однако, в принципе, понятно, как применить одно к другому. В случае Smalltalk и Objective-C объектная модель плоская, допускающая коллизии между селекторами никак не связанных между собой классов, и именно разруливанию этих ситуаций посвящён доклад. А в SOM одноимённые методы классов так конфликтовать не могут, поскольку жетоны методов функционально эквивалентны кортежу из ссылки на класс-объект и имени метода. Однако конфликт возникает в другом месте, если мы пытаемся сделать у каждого класса таблицу виртуальных методов с поиском по индексу. Любое множественное наследование приводит к тому, что на один и тот же слот в таблице виртуальных методов начинают претендовать не подозревавшие о существовании друг друга классы, у которых появился общий потомок. В этом случае можно таблицы виртуальных методов родительских классов раздвинуть так, чтобы потомки не могли конфликтовать, и уже без проблем произвести таблицу виртуальных методов наследника. Приходится попариться при создании классов, зато потом всё летает. Хотя я ещё не исследовал перемычки SOM и не знаю, как оно там было на самом деле.

Интеграция внешней объектной системы в Delphi на примере IBM SOM
Один из моих проектов реализует поддержку SOM в Delphi. Разработка начиналась на Delphi, пришлось часть привязок делать вручную и не так красиво, в процедурном стиле, без проверки типов. Используя эти привязки, был написан генератор привязок в объектном стиле, а затем и сам генератор был переписан на новые привязки, став подтверждением их работоспособности. Ради красоты пришлось хакнуть объектную систему Delphi, и, может быть, вам будет интересно, как это вообще можно делать.

По результатам плотной работы с SOM обнаруживается горькая правда, что множественное наследование там не как в «Putting Metaclasses to Work», CLOS, Dylan и Python, а как в C++. Линеаризации классов, порядка разрешения методов просто нет. Можно либо вызывать только первого родителя среди тех, кто поддерживает метод, либо какого-то конкретного, либо всех, и тогда в ромбовидных диаграммах наследования кому-то будет прилетать многократно. Конструкторы, деструкторы и операция присваивания, чтоб совсем плохо не было, защищаются битовыми полями. Я их и раньше видел, но мне казалось, это связано с тем, что конструкторам может требоваться передавать управление в нестандартном порядке, а обычные методы просто вызовут, что им дал somParentResolve, и попадут куда надо. Эдак довольно солидная часть книжки становится неприменимой к имеющимся бинарникам. То, что описано в книге как то, с чем авторы имели опыт, действительно было таковым, но в общий доступ не попало:
SOM DTK v3.0 Programming Guide на странице 205 (187):
Customizing method resolution requires the use of metaclasses that override SOMClass methods. This is not recommended without use of a Cooperation Framework that guarantees correct operation of SOMobjects in conjunction with such metaclasses. SOMobjects users who require this functionality should request access to the experimental Cooperation Framework used to implement the SOMobjects Metaclass Framework. Metaclasses implemented using the Cooperation Framework may have to be reprogrammed in the future when SOMobjects introduces an officially supported Cooperation Framework.

Раздраконил свой генератор привязок до такого состояния, что уже заработала программа, использующая исключительно новые привязки. Это пример для старых привязок, а это — для новых. В старых привязках SOMObject — это указатель на запись, а все остальные классы — синонимы SOMObject, то есть, контроля типов нет. В старых привязках, чтобы узнать тип аргумента, нужно было в процедурном стиле написать ParameterDef__get_type, а вот имя аргумента объявлялось в Contained, поэтому Contained__get_name, а так как вручную мне лень синонимы везде прописывать, то только по таким именам работало. В новых привязках генератор собирает все методы класса в одну общую кучу, и всё работает достаточно очевидным образом с вызовом методов через точку. И даже атрибуты CORBA спроецировались на свойства Delphi. Поскольку в Delphi жалкое одиночное наследование, генерить список методов требуется каждый раз сначала, и именно это и делается, зато потом удобно.

Работает это посредством того хака, что создаются объекты SOM, а Delphi думает, что это объекты Delphi. Так как с точки зрения Delphi методы статические, Delphi не лезет в VMT, делает статический вызов, в нём написан код, который направит вызов в SOM, и оно там пойдёт куда надо. Ещё немного пошаманил, чтобы волшебным образом работали классовые функции. Классовые функции могут вызываться как у объектов, и тогда Delphi пытается взять из объекта VMT по нулевому смещению, а может — непосредственно у класса, и тогда берётся статический адрес VMT. Учитывая, что с точки зрения Delphi классы друг от друга не наследуются, отличить эти случаи не сложно. Нужно просто сравнить Self со своим именем, и если да, то нас вызвали через имя класса, и класс-объект нужно искать одним способом, а если нет, то нас вызвали как метод объекта, и надо ещё раз разъименовать Self, и там будет лежать указатель на класс-объект.

Немного подкрутил, чтобы в результате метода SOMObject.somGetClass учитывался метакласс. Была мысль вообще все методы метакласса спроецировать на классовые функции и процедуры, но потом посмотрел, сколько там всего в SOMClass, и решил, что не надо. Тем более, что в SOM.IR публично показан только один метакласс, SOMMSingleInstance. Но даже ради него подкрутил, чтобы у метода SOMObject.somGetClass возвращался именно указанный метакласс, а не просто SOMClass. Кстати, так получается, что с точки зрения языковой проекции метаклассы не могут так просто наследоваться. То есть, умом-то мы понимаем, что компилятор SOM гарантирует совместимость метаклассов, но вот во что это спроецировать, открытый вопрос. Если мы наследуемся от двух классов с разными метаклассами, то метакласс потомка будет заведомо принадлежать пересечению множеств потомков каждого метакласса, и что, для этого динамически создаваемого метакласса тоже создавать привязки? А там по цепочке ещё для метаклассов более высокого порядка может потребоваться автоматическое создание. Одно дело — когда движок SOM это создаёт, и другое дело — когда генерится текст. Я решил, что этого делать не нужно, а раз так, то даже, если был класс A с указанием метакласса M_A, и от него наследуется класс B, то, если B не укажет явно тот же метакласс, что и A, в его привязках будет опять обычный SOMClass.

Естественно, все методы обычного TObject не будут работать. Я эту проблему пытаюсь решить, скрывая их при помощи reintroduce в private. Только операция as портит картину. Её не спрячешь, но хотя бы заменил методами, приводящими тип вверх и вниз. Вот так, с небольшой порцией магии создаётся впечатление, что SOM в Delphi как родная. Классы, методы, свойства.

В #2807187 я выяснил, что в Delphi самое сложное — это неразрывный блок type. Все отложенные определения типов к концу блока должны случиться. Поэтому (с точки зрения, где сложнее и рискованнее) поддержка SOM начинается с Delphi, а в самом проекте SOM-Delphi — с блока type. Вот, чего удалось добиться: SOMIRTest.DumpOut.pas. Компилятору Delphi сейчас не хватает только реализации методов, но это уже совсем другое дело. Это не блок type. Пока я тут прыгал с EF на IRF, у меня тут достаточно текстовых заготовок осталось, чтоб написать методы.

Что удалось выяснить: в доках было чётко написано:
For this reason, the use of the -u compiler flag requires that all of the types mentioned in the IDL source file must be fully defined within the scope of the compilation. Warning messages from the SOM Compiler about undefined types result in actual error messages when using the -u flag.То есть, как раз то, что мне не оказалось неприемлемым в EF в купе с невозможностью обработать информацию из нескольких IDL за один проход. Однако, каким-то образом ссылки на отсутствующие классы всё же попали в SOM.IR, на той строке, куда я решёткой поставил ссылку.

Среди расширений CORBA TypeCode kind: внешние типы (tk_foreign), указатели (tk_pointer), циклические ссылки (rk_self). Удалось сделать такую эвристику, чтобы внешние типы по максимуму были определены не сами по себе, а только через указатель. Вот, например, Emitter Framework любит поработать с FILE* из VisualAge C++ RTL. Нашлись методы, которые «указательность» воплощают не через tk_pointer, а через режим аргумента in out, и заставляют алгоритм-таки объявить внешний тип явно. Там тоже немного подкрутил, в Delphi как раз есть синтаксис аргументов "var X" и "out Y" без указания типа. Из четырёх осталось два автоматом объявленных как явные указатели (somId и va_list), и так оно и есть в их случае.

Нашлись и недостатки по сравнению с EF. В EF есть комментарии, а в IRF — нет. Без комментариев уже доки не погенерить, например. Martin Iturbide, владельцу edm2.com, пригодилось бы, он у себя на wiki размещает. Ещё в EF все типы конкретно определены, а в IRF всё делается через CORBA TypeCode. Все пользовательские типы видны как объекты TypeDef, но потом, когда эти типы используются, в CORBA TypeCode видно только конечный результат с учётом всех подстановок. Сохраняются имена только у сложных типов, таких, как записи, перечисляемые и внешние типы, но они содержат не полный идентификатор, то есть, от корня, а просто имя, и его приходится эвристически разрешать, если хотим, чтобы всё было красиво, и эта эвристика, вообще говоря, заведомо менее надёжна. Таким образом, будет не лишне иметь эмиттер, работающий аналогично IR emitter, собирающий всё в одном месте, но просто побольше информации.

SOMIRTest.DumpOut.pas
Ну вот, уже как-то повеселее пошло. Зря не начал сразу с SOM IR. Я тут подсчитал, с учётом крюка через Emitter Framework я вручную написал привязки для почти половины классов (37/75). Классы Emitter Framework чем-то похожи на Interface Repository. И там, и там есть модули (пространства имён), интерфейсы (классы), операции (методы), typedef, но в Interface Repository типы кодируются CORBA TypeCode с нехитрым сишным API, а в Emitter Framework под каждый struct, enum и sequence — отдельный класс. Пока нет генератора, привязывать классы получается дольше.

som

Две основные проблемы встречаются при попытке сделать привязки к SOM-классам. Впрочем, с другими системами до некоторой степени аналогично.
Во-первых, пространства имён (модули в терминологии CORBA и SOM) открытые, добавлять в них может какой угодно файл, и как после этого сделать, чтобы Имя1.Имя2.Класс вёл именно туда, куда надо? Что в Delphi, что в Ada надо сделать пакет Имя1.Имя2, записать в него всё содержимое сразу, и добавить будет нельзя.
Во-вторых, отложенные определения. И не просто отложенные, а этот момент даже может и не состояться. У того разработчика, который потом подключит ещё один модуль, этот момент состоится, а у того, кто не подключит, — нет. Возвращает функция объект какого-то класса, а у него только имя известно и известно, что это имя класса. Вот и что с ним делать? Эти проблемы остановили меня последний раз, как я пытался делать эмиттер.

Интересно подсмотреть, как поступили другие. Пока не имеешь интереса, файлик типа (scemit.ada)[http://octagram.name/pub/somobjects/ada/powerada/contrib/som/bindings/scemit.ada] — это просто набор символов. А теперь смотришь и видишь:
FUNCTION get_somtTemplate (Self : IN Ref) RETURN SOM.SOMObjectPtr;
PROCEDURE set_somtTemplate (Self : IN Ref; somtTemplate : IN SOM.SOMObjectPtr);

в то время, как в оригинале:
#include <sctmplt.idl>
attribute SOMTTemplateOutputC somtTemplate;

То есть, в оригинальном IDL нужный для понимания файл заведомо подключили, а на выходе эмиттера все классы превратились в SOMObjectPtr. Капитально сжульничали! Не люблю такие генераторы. Они, конечно, свою задачу выполнят, но пользоваться этим будет неудобно.

А то, что мне бы хотелось сделать, достигается другим методом: надо натаскать в одно место всю метаинформацию, и с её помощью сгенерить исходники. Причём, в #2773944 я обсуждал такую проблему, что у разных компонент, разрабатываемых независимо, может быть разный взгляд на классы, и если делать общие для всех компонент привязки, то разные bpl будут конфликтовать, хотя SOM призван именно эти конфликты не допускать. Вот заодно натасканную со всех мест метаинформацию можно будет выплеснуть в пространство имён, специфичное для компоненты. Пытался добиться этого эмиттером, но полностью разочаровался в Emitter Framework в том виде, как он есть в SOM. Я видел, как в некоторых ситуациях компилятор SOM вызывается пофайлово, а в некоторых других — сразу для нескольких файлов. Например, эмиттер imod из нескольких .idl делает файл, инициализирующий все классы в DLL. Я думал, это то, что нужно, и когда нужно, я этим воспользуюсь. Даже примерно понятно, как это делать. Созданный newemit эмиттер реагирует на классы и модули, но если печатать другие события, то оказывается, что есть ещё SOMTEmitterBeginE, SOMTEmitterEndE. Но только они. somdtype.idl ничего нового не выдал. Теперь, казалось бы, надо принимать информацию о модулях и классах в память, а потом в последний момент при событии SOMTEmitterEndE всё вывалить в файлы. Но не тут-то было. Компилятор SOM для каждого .idl запускает эмиттер отдельно, даже если указать их в одной командной строке. Напрашивается вопрос, а как же тогда работает эмиттер imod? Ведь ему надо сразу всю инфу. Оказывается, он инкрементально перезаписывает текстовый файл. Находит по сигнатуре строки и дописывает перед ними. Там несложно, а делать такое же для удобных языковых привязок я офигею.

Так что тут надо использовать другой инструмент, SOM IR. Сначала эмиттером ir делать (он умеет инкрементально) бинарный som.ir, потом с него синтезировать исходники, полностью аналогично тому, как в COM привязки делаются из TLB. Заодно, как я тут выяснил, эмиттер ir проверяет изменения на обратную совместимость. Также это значит, что я смогу экспериментировать с somFree. Раньше он мне не подходил, потому что там Emitter Framework не было, а у меня исходники в первую очередь для него делались, и без генератора привязок я не мог дальше продвинуться, а сейчас, с учётом того, что как ни крути, только из SOM IR можно делать нормальные привязки, эта проблема перестаёт быть такой актуальной.

Saba Zamir. Handbook of object technology
В апреле 2015го года я похвастался покупкой этой здоровенной книги ( #2778493 ) и посожалел, что нет в электронном виде. Но вот теперь вижу, что в Интернете появилась цифровая, даже, похоже, не отсканированная, а из оригинала версия этой книги. На 512й странице там про SOM, а перед этим — про CORBA, а после этого — про COM. И про Objective-C есть. Всё как надо.

emitcom: An Emitter of COM Interfaces
Некогда я с Internet Archive понакачал всяких разных .zip, и какие–то из них с документацией к SOM 3.0 Beta, но в формате PostScript, а мне как–то лень было их перекодировать, не думал, что там что–то особо новое найду. Сегодня собрался и–таки нашёл. Мучил меня вопрос, как делать ODdual классы, а конкретно, там указаны ограничения на типы данных, которые допустимы в ODdual классах, но эти типы нигде не объявлены. Я, конечно, могу написать что–нибудь вроде typedef somToken BSTR, но как ctypelib поймёт, что это именно тот BSTR? Все BSTR будут интерпретироваться как UTF-16 строки или есть какой–то .idl для SOM, в котором BSTR объявлен и помечен так, чтобы проецироваться в нужный тип COM? Не меньшее удивление вызывали IUnknown и IDispatch. В состав OpenDoc 2.1.4, по крайней мере, такой IDL не входит, я всё перерыл, а если в бете было ещё одно средство интеграции SOM и COM, то, может быть, и основные типы OLE были объявлены в IDL, который был в составе SOM 3.0 Beta.