Очень хороший вопрос. Давайте разберёмся как устроена динамическая модульность в системе ETH Oberon (а заодно и в ETH PlugIn Oberon for Windows) и как её можно использовать с помощью OPCL.
Закономерно, что не все модули ETH Oberon являются динамическими: ведь для того, чтобы загрузить модуль (из файла в память) нужно как минимум уже иметь средства для распределения памяти и работы с файлами. Поэтому модули Kernel и Files быть динамическими не могут и не должны, как и некоторые другие модули. Загрузчик ОС (или подсистема исполнения Windows PE - Portable Executables) загружает такие модули в память своими личными низкоуровневыми средствами (средствами вышележащей ОС или же, например, путём вызова прерываний BIOS или как-то ещё). Вобщем, примем как факт, что часть модулей спроектированы не для динамической загрузки и уже находятся в нашем распоряжении (в случае с Windows, которая не имеет в своём составе рантаймов для поддержки Оберон-окружения, такие модули могут быть вкомпилированы в целевой исполняемый файл).
Мы можем без труда выяснить, что за динамическую загрузку и выгрузку модулей отвечает модуль Modules:
Код: "OBERON"
MODULE Modules; (** portable, except where noted *)
(**
The Modules module implements the dynamic module loader of the Oberon system.
It is responsible for loading and freeing modules.
*)
IMPORT Kernel32, Kernel, Registry, FileDir, Files, SYSTEM;
Очевидно, все импортированные здесь модули должны быть доступны ДО загрузки любого динамического модуля.
Вот эта процедура:
Код: "OBERON"
(** Returns a handle to an already loaded module,
or if not loaded, loads the module and all its imported modules. *)
PROCEDURE ThisMod* (name: ARRAY OF CHAR): Module;
получает на вход имя загружаемого модуля и возвращает указатель на дескриптор модуля.
Процедура:
Код: "OBERON"
(** Free a module from memory. Only modules with no clients can be freed.
The all flag requests that all imported modules should be freed too (i.e. a recursive call to Free). *)
PROCEDURE Free* (name: ARRAY OF CHAR; all: BOOLEAN);
выгружает указанный модуль (и все зависимые от него, если all = TRUE).
Осталось понять как этим пользоваться. Я нашёл такой пример использования динамической загрузки в модуле ETHOberon/Src/Printer.Mod:
Код: "OBERON"
Mod := Modules.ThisMod("WinPrinter"); (* Загружаем динамически модуль WinPrinter *)
IF Modules.res = 0 THEN (* Если успешно загрузился, то: *)
Cmd := Modules.ThisCommand(Mod, "Install"); (* Ищем в нём команду
(процедуру без параметров) установки "Install" *)
IF Modules.res = 0 THEN (* Если она там присутствует, то: *)
Cmd() (* Запустим её *)
END
END
В идеале хорошо бы состряпать минимальный примерчик, который бы показывал динамическую модульность в действии, однако,
Len, оставляю это на Ваше усмотрение. В мире Оберона мало готовых решений, тем он меня, например, и привлекает.
Теперь далее. Я не рыл очень уж досконально потроха ETH Oberon, но полагаю, что в какой-то момент с помощью Modules.ThisMod загружается динамический инициализатор Оберон-системы, и он уже в свою очередь пользуется для загрузки/выгрузки модулей только этим механизмом (т.е. все последующие модули уже динамические).
Теперь немножко о том как реализовывать свои плагины. Пишете модули с одинаковым интерфейсом. Папочкой для них будет абстрактный модуль-контейнер, экспортирующий виртуальную запись, в которой методами будут указатели. Реализации этого интерфейса будут импортировать контейнер (но не наоборот!). Инсталлятор реализаций будет инициализировать эту запись (подключать в абстрактный слот контейнера нужную реализацию, при необходимости подгружая соответствующий модуль). Совместимость интерфейсов гарантируется совместимостью типов виртуальной и реализованной записи. Если нужно расширение, унаследуем от этой записи и расширим её дополнительным функционалом. Примеры смотрите в BlackBox'е (SystemFiles и HostFiles, SystemDialog и HostDialog, и т.д.).
В качестве теоретического материала советую главу "
6. Загрузчик модулей" из книги
"Разработка операционной системы и компилятора. Проект Oberon", стр. 183-197.