to post messages and comments.

Собрался наконец положить конец неотсканированности книги «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.

Установил OpenDoc на Windows 2000, так как на более новых были проблемы это сделать (16–битный InstallShield, например), а формат установочных файлов такой, что не подкопаться. Поизучал. Увидел внутри SOM Collection Classes, правда, под именем odsomuc.dll, а не somuc.dll. Таким образом, до сих пор не найденная SOM 3.0 Beta уже ничего уникального не содержит. Event Manager (somem) нашёлся в SOM 2.1, который я извлёк из VisualAge C++ v3.5 FixPak 9. Коллекции — вот здесь. Вот и всё. Я очень рад, что больше ничего нельзя потерять. Надо, не надо — потом разберёмся, главное, что есть.
Для интеграции между SOM и COM, а также между OpenDoc и OLE применяется утилита ctypelib. При этом вызов DllGetClassObject находится в odole.dll, а некоторые рабочие механизмы — в oddsole.dll, например, я там вижу такие строки, как ISOMObjDispatch, ISOMObjRef, ISOMObjClassFactory. Вот эту систему я бы очень хотел посмотреть в действии. Насколько я понял, сначала мы вызовом sc.exe -sir крафтим из наших .idl, SOM Compiler (sc.exe) и SOM Interface Repository (emitir.dll) файлик .ir. Этот файл по свойствам похож на COM'овский .tlb, но только .tlb неизменен, а .ir редактируем. Затем вызывается ctypelib. ctypelib смотрит переменную среды SOMIR, эта переменная содержит разделённый ; список файлов .ir, среди них должен оказаться и наш файлик, в нём ищется указанный модуль и на основе этой информации создаётся TLB для COM. Если ничего не предпринимать, то вызовы из COM в SOM работают только через IDispatch, но можно пометить свой класс ODdual, приведя его, конечно, в соответствие, и его проекция в COM станет дуальной. Я думал, тут генерятся какие–то C файлы, ан нет, всё в рантайме, похоже. Любопытно.
Есть и признаки того, что пишущие для OpenDoc разработчики не вполне приняли идеологию SOM. При том, что там всё на классах SOM, здесь читаем:
The generally preferred procedure is to create only one SOM class, a subclass of ODPart whose interface contains nothing but your public methods (overrides of the methods of ODPart and its superclasses). That SOM class delegates all of its method calls to a C++ wrapper class, which contains the functionality for the public methods as well as any private fields and methods. Additional classes can be subclasses of your C++ wrapper class.
Казалось бы, зачем заворачивать класс в класс, если уже и так есть класс? Хотел бы я посмотреть на разработчиков под Mac OS X, которые бы в качестве preferred procedure заворачивали так же класс в класс. Кроме тех болезных, конечно, которые портируют уже написанное не–OpenStep приложение. Ещё один аргумент в пользу того, что DTS C++ поздно подоспел. Разработчики как–то не уловили, что SOM — это и есть то, что нужно для классов, и какие–то другие не–SOM классы им не нужны.

По идее, OpenDoc воплощает Open Scripting Architecture, ту самую, что в современной Mac OS X, ну разве что OSAEvents, а не AppleEvents. Правда, на уровне библиотек я так и не увидел, в чём это выражается. Я думал, что OSA — это аналог JSR-223 для нативного кода, но, похоже, это нечто более абстрактное.

А было бы кому–нибудь интересно, если бы я реализовал SOM для JavaScript? Множественное наследование, как в CLOS, метаклассы.

Для тех, кто не знает, в чём разница между множественным наследованием, как в CLOS, и как, например, в C++ (и некоторых других JavaScript библиотечках, которые я тут посмотрел на тему МН):
В C++ для вызова родительского метода он указывается по имени родительского класса. Два родителя — делай два родительских вызова или ещё как–нибудь. Или делай только один из них, и однажды это запорет систему. Или не делай ни одного, но не знающий об этом программист другого компонента может сделать ромб в структуре классов и, чтобы не вызывать код дважды, вызовет только метод твоего класса, и всё запорется. Пример:
// Call the super constructors of the base classes.
Person.call( this, "Ben" );
Monkey.call( this, true );

Для сравнения: в CLOS и SOM родительский вызов — это специальная форма вызова, в которой имя родителя НЕ указывается. В DTS C++ в том виде, как он описан в «Putting Metaclasses to Work», пришлось добавить синтаксис __parent->method(). Так называемые кооперативные методы, будучи переопределены, ОБЯЗАНЫ вызвать родительский метод, за исключением случаев вроде такого, когда цепочка вызова методов образует коньюнкцию или дизъюнкцию, и нам попался false или true, соответственно. Для каждого класса все его родители выстраиваются в упорядоченный список (class precedence list в CLOS, method resolution order в SOM), и семантика вызова родительского метода — это, на самом деле, вызвать реализацию из предъидущего по списку класса, который в случае наследования ромбом может быть более левым родителем дочернего класса, или ещё где–нибудь. Некооперативные методы можно писать иначе, но компилятор потом может запретить наследование из–за потенциального конфликта. Впрочем, в собственно SOM я не видел проверок на кооперативность и я не видел таких пометок у методов, так что некооперативными методами (не вызывающими родителя или вызывающими конкретного родителя/родителей), как я понимаю, при желании всё–таки можно что–нибудь запороть.

За основу можно взять Java–симуляцию, которая прилагалась к книге: bitbucket.org

Project Centennial: Converting your Normal Windows App (Ada, Delphi, SOM) to a Metro Windows App for Distribution in the Windows Store
В Microsoft наконец зачесались, а чёй–то так мало приложений под Metro, наверное, средства разработки были трешовые, значит, надо дать возможность нормальными средствами разработки делать приложения для Metro

OpenDoc 1.2.4 для Windows
Не смотрел пока, что внутри. По идее, там должен быть ComponentGlue, при помощи которого можно из VBScript, JScript и т. п. обращаться к объектам SOM, и если сложить DTS C++ и ComponentGlue, можно написать впечатляющую неискушённых статейку и выложить бинарники, которые желающие могут потестить на Windows (альтернативы — AIX, OS/2, Mac OS Classic — менее практичны)

Novell ComponentGlue
Оказывается, гипотетический мост между SOM и COM не такой уж и гипотетический! Точнее, там не сам COM, а OLE Automation, что тоже весьма интересно. Это расширяет список из 8ми известных мне языков программирования, для которых была прямая поддержка SOM, до практически всех языков программирования, способных делать OLE Automation вызовы.
OpenDoc parts can be incorporated inside as OLE2 application
OLE 2 applications can be included within an OpenDoc application
OpenDoc scripts, which control a component's behavior, can drive OLE 2 components; OLE Automation scripts can drive OpenDoc parts.
Every OpenDoc part is an OLE Control (OCX), which can be used by any OCX tool, such as Microsoft Access.

VisualAge C++ v3.5.7 с патчем до 3.5.9
Наконец–то получил именно тот компилятор, который лучше всего работает с бимерской версией SOM. Теперь его хоть установить по–человечески можно.
Ожидал, что внутри увижу REXX.EXE, но увы, его там всё также нет, а есть только REXX.DLL, REXXAPI.DLL, REXXUTIL.DLL, и в них видна интеграция с SOM.

web.archive.org
The new VisualAge for Basic also incorporates IBM System Object Model (SOM)* technology, which allows applications to access and use diverse software components, even when they are written in different programming languages. Development becomes easier because SOM technology provides a language-neutral programming environment and manages local and remote communication among objects.Список языков увеличился до 8. Есть подозрения, что и для PL/I что–то было сделано, вместе с ним могло быть 9

som

Посмотрел примеры работы с памятью: octagram.name
SOMFree(inParameter); inParameter = NULL;
/****************************************************************************
* ORBfree — for some out parameters and returned values, corba specifies the use of
* ORBfree to free DSOM allocated storage, for DSOM may use special memory
* management techniques to allocate memory. Storage so allocated must be treated spcially
* by the user; specifically, pointers within it may not modified, not can they be freed using
* SOMFree and must be freed using ORBfree.
**************************************************************************/
ORBfree(outParameter); outParameter = NULL;
SOMFree(inOutParameter); inOutParameter = NULL;
ORBfree(returnValue); returnValue = NULL;
Вот чёт не нравится мне, что то ORBfree, то SOMFree. И с объектами похожая ситуация. После Objective-C и COM как–то привык к прозрачности выполнения, локальные объекты смешиваются с удалёнными, а в случае с DSOM надо точно знать, где локальные объекты, где удалённые, где удалённые копии локальных объектов, где локальные копии удалённых. Возможно, эта мешанина возникает из–за требований CORBA, в которых возможность взаимодействия внутри процесса не оптимизировалась. Я за свой стаж разработчика по–взрослому в RPC не погружался, и я–таки действительно всегда знал, где локальные объекты, где удалённые, но желание некоторой прозрачности всё равно было. В Apple версии были какие–то изменения относительно IBM версии, и если возрождать SOM, то скорее Apple версию, чем IBM. somFree, насколько мне известно, включает API Apple SOM.

В том самом патче 3.5.9 ( #2766863 #2781844 ) для VisualAge C++, я так понимаю, содержится полная версия SOM 2.1. В его файлах, а именно в include\SOMTDBCS.C, нашёл такое:
#ifdef _WDOS
#include <dos.h>
#define DOS_GET_DBCP 0x6601
#define DOS_GET_DBCSEV 0x6300

static int GetKBCodePage(void)
{
union REGS regs;
regs.x.ax = DOS_GET_DBCP;
intdos(&regs, &regs);

return (regs.x.bx);
}
#endif / _WDOS /
Таким образом, нашлась уже восьмая OS, на которую был портирован оригинальный SOM.

public.dhe.ibm.com
Добрался потестировать на чистой машине. Этот патч, как и другие, сделан путём замены одних файлов другими, но на этот раз заменяющих файлов так много, что этого хватит на то, чтобы запустить компилятор. Проверил эту гипотезу.
1) Разпаковал в C:\home\OCTAGRAM\DTS
2) Запустил отдельную командную строку
3) Выполнил set SOMBASE=C:\home\OCTAGRAM\DTS\ibmcppw
4) Запустил C:\home\OCTAGRAM\DTS\ibmcppw\bin\SOMENV.BAT
5) Сделал cd C:\home\OCTAGRAM\DTS\ibmcppw\samples\compiler\dts
6) Для надёжности nmake clean
7) nmake
8) hhmain.exe так просто не запускается, так как .dll для него собирается в другой каталог, поэтому я сделал set PATH=%PATH%;C:\home\OCTAGRAM\DTS\ibmcppw\samples\compiler\dts\xhmain\dtsdll
9) Запустил hhmain.exe, получил:
C:\home\OCTAGRAM\DTS\ibmcppw\samples\compiler\dts>hhmain.exe
Local anInfo->x = 5
Local anInfo->_get_x() = 5
Local anInfo->y = A
Local anInfo->_get_y() = B
{An instance of class info at address 0092E318

}

С виду, всё в порядке. Таким образом, у нас есть возможность пощупать настоящий Direct-to-SOM C++. Если бы в своё время такой C++ среди других C++ стал мейнстримом, он бы не стал такой каждой бочке затычка. И другим языкам не мешал бы, и в тупик не был сейчас загнан необходимостью совместимости.

octagram.name
Есть такая книга, Saba Zamir, «Handbook of Object Technology», в моей квартире толще неё, наверное, только БСЭ, там обо всём помаленьку. Нравится мне тем, что в этой книге нашлось места и для Ada, и для SOM, и для Object Pascal, и ещё много чего, о чём я не знал, но, может быть, найду время почитать.

Для своей эпохи довольно важная, каждый, кто хотел иметь отношение к объектным технологиям, стремился в эту книгу попасть (Saba Zamir там в роли редактора, а у каждой главы свой автор). Полное оглавление можно посмотреть здесь и даже купить отдельную главу за $20. Многие главы после публикации в книге были переопубликованы в Интернете, такие главы я надёргал отдельными файлами. Например, много, где лежит PDF «An Overview of the C++ Programming Language» под авторством Ricardo Devis, Bjarne Stroustrup, и там в самом начале написано «From The Handbook of Object Technology (Editor: Saba Zamir). CRC Press LLC, Boca Raton. 1999. ISBN 0-8493-3135-8» — как раз из этой книги. А какие–то главы, которые были мне особо интересны, я надёргал в Google Books: books.google.ru
Делать это получалось с переменным успехом, Google показывает не все страницы, но для разных IP скрыты разные, причём, я года два назад трёх провайдеров поменял, и ещё с работы заглядывал, но так и не все страницы взял. Сегодня ещё раз заглянул с ещё одного места, и белые пятна в интересных мне главах практически ликвидировал. Одна страница с парой–тройкой элементов списка литературы осталась скрытой. Переживём как–нибудь без списка литературы, я этой литературы всё равно накачал изрядно, читать — не перечитать.

Интересовал меня раздел V Object Technology Standards and Distributed Objects, а в нём главы с 28ой по 32ую. Там и про CORBA, и про SOM, и про DSOM; а самая последняя глава — про COM. Там я вроде тоже всё знаю, но на всякий случай выдернул и её тоже. Другой интересный раздел был посвящён ООП в разных языках программирования. То, что касается Ada 95, выдернул почти всё, ещё на всякий случай Modula-3, SmallTalk, Objective-C, но там с пробелами. Про Object Pascal я и так всё знал, поэтому даже не выдёргивал. Если в Google Books много страниц посмотреть, он начинает прятать всё большими кусками, так что когда пролистал то, что мне интересно больше всего, остальное оказывается скрыто. Физически книга есть у меня дома ( octagram.name ), если что–то ценное, могу так почитать или отсканировать.

До сих пор загадка, как это IBM так сдулись, и их технологии вычеркнулись отовсюду, как в «Черновике» Лукьяненко или как в Потерянной комнате. Своим физическим присутствием книга работает как свидетельство. Собственно описание SOM там гораздо проще, чем остальное, что я читал по этой теме.

Есть в Delphi и Ada приятная плюшка, собирать несколько пакетов в динамические библиотеки для совместного использования (.bpl в Delphi). Приятно это тем, что всё прозрачно. Вчера собирал всё статически, завтра решил, что вот этот, этот и вот этот пакеты должны быть отдельно в другой динамической библиотеке, и это вынесение происходит без лишних изменений кода. Не приходится вставлять макросы типа «если объект динамической компоновки в этом проходе такой–то, то экспортируем эту функцию, иначе импортируем её». Но вот есть ещё SOM, который решает проблему хрупкого базового класса и другие аналогичные проблемы, которые возникают хоть в C++ библиотеках, хоть в .bpl, они в этом плане не отличаются. Если делать SOM эмиттеры для Delphi и Ada, лучше с самого начала предусмотреть ситуацию, когда .bpl и SOM .dll применяются одновременно, так как хрупкость .bpl может нивелировать преимущества SOM. То есть, допустим, реализован у нас какой–то SOM класс в каком–то юните, который попал в какой–то .bpl и оттуда переэкспортируется в .dll, откуда может быть использован SOM потребителями. А юниты, соседствующие с юнитом SOM класса внутри .bpl, конечно, будут искать структуры ClassData не через .dll, а обращаться напрямую в юнит. Кроме того, так могут поступать и юниты из соседних .bpl. И тут может быть такая проблема, что интерфейс SOM класса мы поменяли, и SOM это изменение поддерживает, вот только интерфейс .bpl изменился, и это изменение может сломать весь код, который ходил в структуры ClassData через .bpl в обход SOM. С другой стороны, если попытаться это исправить наивным способом, то даже .bpl будет ходить в свой собственный класс через .dll, что не должно происходить. Пока я решение вижу в том, чтобы отрезать все меж–.bpl'ные связи между компонентами, которые собираются независимо. То есть, если у нас скрипт собирает 3 .bpl синхронно, между ними связь разрешена, иначе нужно ходить через .dll. Теперь надо подумать, как это лучше сделать. В реализации CORBA для Delphi, например, делается три разных юнита с разными суффиксами для каждого класса, один для импорта, второй для экспорта, и ещё третий для чего–то. Но CORBA клиент и сервер обычно живут в разных процессах, а SOM — это внутрипроцессная CORBA. В моём случае юнитов, импортирующих одно и то же, будет столько же, сколько и независимо собираемых компонентов. Вплоть до того, что у каждого, получается, должно быть своё видение SOM API. Либо будет NoBPL версия, которую нельзя заворачивать в .bpl, а для .bpl нужно автоматизировать клонирование импортирующих юнитов в другое пространство имён. То есть из SOM.pas делается Baz.SOM.pas, из SOM.ClassManager.pas делается Baz.SOM.ClassManager.pas, и uses у них внутри переписываются. Либо надо посмотреть, можно ли включить юнит внутрь .bpl, но чтоб он наружу не светился, тогда этого делать не придётся. У всех независимо собираемых компонент будет свой взгляд на мир за счёт статического включения импортирующих юнитов, но не придётся называть эти юниты по–разному.

som

www-03.ibm.com
Нашёл на сайте IBM ещё несколько добротных PDF'ок. Судя по содержанию, некоторые из них — это более новая версия других, которые я уже видел. Нашёл в одной из PDF'ок, как SOM compiler печатает свою версию 2.55 и дату своей компиляции, сравнили вместе с valerius (irc.efnet.net #osFree KOI8-R) это с информацией из SOM 3.0 для WinNT и SOM 2.1 для OS/2. В SOM 3.0 компилятор версии 2.55, в SOM 2.1 — версию 2.54.
По всей видимости, внутри IBM (Apple Copland и Himalaya NonStop OS относительно независимы от IBM) самый новый SOM оказался на OS/390. Правда, технических усовершенствований я там не заметил, а вот то, что документация стала более полной — это и для обычного SOM полезно. Вот, в частности, стал больше раздел про Emitter Framework. Оказывается, при помощи SOM_Substitute можно затенять классы, представляющие узлы синтаксического дерева. Я это ещё не пробовал, но если это работает, то интересная возможность. Работать это может только в том случае, если классы грузятся средствами SOMClassMgr, а обычно это не так. Более объёмные примеры.

upload.wikimedia.org
Вот так выглядит могила, в которой IBM похоронил SOM. По крайней мере, с моей, обывательской в данном случае, точки зрения, похоронил. Даже Open Street Map, недавно праздновавший переполнение 32–битных интов, как–то смог обойтись без OS/390, z/OS и т. п.

som

А операционок, на которые был портирован SOM, насчитал 7: OS/2, AIX, WinNT, Mac OS Classic, Copland, OS/390, Himalaya NonStop OS. Это оригинальный SOM. somFree портирован на более знакомые названия.

public.dhe.ibm.com

Нашёл на серваке IBM такое. FixPak для VisualAge представляет из себя не слишком замудрёный формат, там просто все отличающиеся файлы в .zip'е. К сожалению, тех файлов, которые не отличаются, там нет.

Во–первых, это первый раз, когда я вижу в VisualAge C++ for Windows хоть что–то, напрямую связанное с SOM. То есть, разрыв произошёл между версиями 3.5 и 3.6. В VisualAge C++ 3.6.5 я уже не смог найти чего–то, напрямую связанного с SOM. Ни дистрибутива, ни примеров, как будто и не было ничего. Какой–то флаг у компилятора остался, не понятно, на что влияющий.

В этом архиве содержится SOM 2.1. Возможно, даже в полном составе.

Из того, что есть в этом архиве, могут быть интересны:
ibmcppw/include/EMAN.IDL — подсистема событий была вырезана из SOM 3.0 Release.
ibmcppw/include/SOMSSOCK.IDL — SOM интерфейс для работы с сокетами. Вообще не знал про такое.
ibmcppw/include/SPERSIST.IDL — SOM интерфейс для объектов, хранимых в базе данных. Видимо, для SOM была своя OR/M, но я про это мало знаю. Что–то читал про то, что IBM от одного стандарта постоянных (Persistent) объектов пыталась перейти на CORBA'овский Persistent Object Store (POSSOM), но в SOM 3.0 Release вырезали и старое, и новое.
ibmcppw/samples/compiler/dts — примеры для компилятора VisualAge C++ в режиме Direct-to-SOM. som.dll без DTS C++ — как objc.dll без Objective-C. То, что Direct-to-SOM C++ появился настолько позднее SOM — одна из причин, почему SOM не был понят и принят в своё время.

public.dhe.ibm.com

iocsrc/iocsrc/CPPWOB3/iiaset.h — здесь можно видеть косвенное подтверждение тому, что в IBM хотели посредством DTS C++ перевести Open Class Library с рельс C++ на рельсы SOM. Без этого кажется, что в IBM одна рука не знала, что делает другая рука. Одно отделение делает SOM, а другое делает OCL на старой технологии, то есть, на C++. А ещё этот файлик мог стать прообразом для somf_TSet, но заметного соответствия между иерархией классов коллекций SOM 3.0 Beta и иерархией классов коллекций OCL я не вижу.

Во–вторых, если удастся запустить компилятор, у нас появляется больше библиотек, на которых можно потестировать эмиттеры. Не у всех классов OCL есть поддержка SOM, но хоть что–то. А ещё можно для Хабрахабра что–нибудь написать. Предположительно, для Хабрахабра поначалу будет лучше обойтись без возни с эмиттерами. DTS C++ сравнительно хорошо показывает, что в голове у того, кто пишет для SOM, потому что обычный компилятор SOM генерит горы макросов и развесистых файлов привязок, за которыми можно потерять простую суть.

В–третьих, и ещё какие–нибудь библиотеки типа wxWindows можно будет попробовать преобразовать в SOM хотя бы на Win32, в качестве proof of concept.

Попытался запустить симуляцию из «Putting Metaclasses to Work» на современной Java. В той Java, для которой писал автор книги, видимо, ещё не было java.util.List, поэтому возникает коллизия между import java.util.; и import putting.om.;, что легко пофиксить, указав явно import putting.om.List;
bitbucket.org — а вот тут получается NullPointerException, который не понятно, как поправить по–простому.
docs.oracle.com — этот класс является родительским для ClassReference. При этом в доках не рекомендуется применять в качестве ключей что–то кроме строк, а в симуляции это типичная ситуация. Наверное, в старой Java это работало, а сейчас — уже нет. В крайнем случае можно надыбать достаточно старую Java, на которой всё запустится.

bitbucket.org
Прокушев раззадорил, решил тоже хоть куда–нибудь продвинуться. Почитал SOM Technical Overview, страница 151, выполнил newemit, переделал полученный C код в Delphi, собрал emitdelphi.dll, и вроде бы оно даже не падает, как это обычно бывает, если перепутать уровень косвенности или ещё что–нибудь. Ничего особенного при этом пока не делается, сам код весь заглушечный, просто дампит в выходной файл всё, что через него проходит.

В продолжение #2761490
Идея сделать такой компилятор C++, который уже существующие библиотеки скомпилирует в SOM, достаточно заманчива, чтоб от неё так просто отказаться. Но делать это придётся эвристически. Учитывая, что нормального множественного наследования в C++ нет, его толком никто и не применяет, а то, что применяют — это Адско–Джавовская модель со множественным наследованием интерфейсов и одиночным наследованием классов, и отличать класс от интерфейса придётся эвристикой, ну или как в Asm.js, выполнять дополнительные проверки, отличающиеся от стандартов языка–носителя. То есть, если одиночно–наследованный класс (за неимением альтернатив в стандарте C++) делает родительский вызов поимённо, такой вызов эвристически заменяется на родительский вызов SOM, который может привести отнюдь не к тому классу, который был указан по имени в исходном коде. А если вызываются несколько родителей, останавливать компиляцию, так как такой случай не предусмотрен.