Как реализовать систему плагинов C ++ для модульного приложения? - PullRequest
0 голосов
/ 20 июня 2020

Я пытаюсь разработать модульное приложение; один, где разработчик создает плагины в виде DLL, которые связываются с основным приложением с помощью loadlibrary () или dlopen (). Мои текущие мысли по этому поводу:

1: И приложение, и подключаемый модуль включают основной заголовок с чистым виртуальным классом IPlugin с метод run () . Затем модуль подключаемого модуля реализует класс, определяя run () :

2: Модуль подключаемого модуля определяет функцию IPlugin * GetPlugin () используя «extern c» для обеспечения совместимости с ABI

3: Приложению требуется подключаемый модуль, использующий loadlibrary (), извлекает IPlugin из GetPlugin ( ) с использованием getprocaddress ()

4: Приложение вызывает метод run () для запуска плагина

Все это работает, но как мне создать интерфейс плагина, который будет иметь доступ ко всему коду моего приложения? Если у меня есть класс, который создает окно или инициализирует кнопку, я хочу получить к ним доступ через интерфейс плагина и иметь возможность манипулировать ими и получать события, при этом память по-прежнему будет управляться стороной основного приложения.

1. Как мне go это сделать?

2. Будет ли лучшее решение совместимо с ABI?

1 Ответ

0 голосов
/ 20 июня 2020

API использует либо C ++, либо C, золотой середины нет. Если вы хотите заставить разработчиков плагинов использовать компилятор, совместимый с ABI, тогда у вас может быть интерфейс, который имеет дело с классами и виртуальными методами, и нет необходимости в каких-либо шумах «extern C». Только API C и C API. Конечно, вы можете реализовать таблицу виртуальных функций, используя структуру указателей функций, а внутри вы можете обернуть ее в класс C ++ и еще много чего. Но это детали вашей реализации, которые не фигурируют в дизайне самого интерфейса плагина.

Вот и все. Нет такой вещи, как совместимость с C ++ API бесплатно на Windows. Люди используют как минимум 5 несовместимых компиляторов - MSV C 2017+, 2015, 2012, mingw g ++ и clang. Некоторые особо захолустные корпоративные учреждения будут иногда настаивать на использовании даже более старых MSV C. Таким образом, интерфейс C ++ - это в основном безнадежное дело, если вы не предоставите прокладки для всех этих пользователей. Это немыслимо в наши дни непрерывной интеграции (CI) - вы можете легко создавать оболочки, использующие ваш C API, и предоставлять их пользователям через интерфейс C ++, совместимый с их предпочтительной системой разработки. Но это не значит, что вы можете возиться с их объектами C ++ прямо из вашего кода C ++. По-прежнему существует посредник C, и вам все еще нужно использовать помощники в коде адаптера. Например, вы не можете удалить объект, предоставленный пользователем напрямую - вы можете сделать это, только вызвав помощник в DLL адаптера - помощник, который использует ту же среду выполнения и совместим с ABI с кодом пользователя C ++.

И не забудьте, что для любой данной версии компилятора существуют несовместимые двоичные библиотеки времени выполнения - например, отладка против выпуска и однопоточная против многопоточной. Итак, для любой версии MSV C вы должны предоставить четыре библиотеки DLL адаптера, с которыми разработчики подключаемых модулей могли бы связываться. И затем ваш код также ссылается на этот адаптер для доступа к пользовательскому плагину. Таким образом, вы должны сначала проанализировать двоичные заголовки в плагине, чтобы определить, какую DLL адаптера и среду выполнения он использует, и выдать сообщение об ошибке, если они не совпадают (разработчики плагинов, скорее всего, испортят). Затем, если есть совпадение, вы загружаете DLL плагина. Компоновщик Dynami c принесет необходимую DLL адаптера. После этого вы готовы использовать DLL адаптера для доступа к подключаемому модулю.

Сделав это раньше, я советую, чтобы каждая dll адаптера предоставляла различные символы C вашей основной программе, так как всегда будут несколько плагинов, каждый из которых использует другой адаптер, и это только усложняет ситуацию. Вместо этого вам нужно подключиться ко всем адаптерам через загрузку по запросу на Windows и получить доступ к конкретному адаптеру только после анализа библиотеки DLL плагина, чтобы узнать, что он использует. Затем, когда вы вызываете адаптер, компоновщик Dynami c разрешит заглушки по запросу для реальных функций адаптера. Аналогичный подход может использоваться на платформах, отличных от Windows, но требует написания вспомогательного кода для обеспечения функции ссылки по требованию, поэтому может быть проще всего использовать dlopen явно на Unix. Вам все равно потребуется проанализировать заголовки ELF плагина, чтобы определить используемую им среду выполнения C ++ и библиотеку адаптера, которую он ожидает, проверить комбинацию; и только потом загрузите его. А затем вы откроете адаптер, чтобы поговорить с плагином. Ни в коем случае вы не будете напрямую вызывать какие-либо функции в самом плагине - только адаптер может сделать это безопасно, когда вам нужно пересечь границы времени выполнения C ++. Могут быть более простые способы сделать все это, но мой опыт показывает, что они работают только на 99%, так что в конечном итоге здесь нет дешевого решения - если только кто-то не написал проект (или продукт) с открытым исходным кодом для всего этого. Ожидается, что вы поймете грязные детали реализации и все равно справитесь с ошибками времени выполнения C ++, если попробуете это. Так что, если вы предпочитаете избегать всего этого - не связывайтесь с API-интерфейсами, видимыми для пользователя C ++, для которых требуются удобные библиотеки.

Вы, конечно, можете создать мост только для заголовков C - для C ++ для пользователя, но, как ни странно, некоторым пользователям не нравятся решения только для заголовков. Вздох. Откуда мне знать? Мне пришлось избавиться от интерфейса только с заголовками, чтобы повысить настойчивость клиентов… В тот день я плакал и смеялся как мания c одновременно.

...