Портирование COM на Linux

от автора

Мне нравится технология COM. Но речь пойдет не о технологии, восхвалении или недостатках COM, а опыте переноса и реализации на Linux. Велосипед? Целесообразность? Давайте не будем на этом заострять внимание.

COM-объект (1)

В общем понимании, объект класса, реализующий как минимум один COM-интерфейс. Реализация объекта в основном скрывается в динамически подключаемой библиотеке, называемой COM-сервер(2), для использования публикуются и распространяются интерфейсы.

COM-интерфейс, абстрактный класс содержащий только чисто виртуальные функции. Выделяется особый интерфейс IUnknown, любой COM-объект обязан реализовывать данный интерфейс.

Каждый COM-интерфейс должен содержать некий свой идентификатор. В COM он определяется структурой GUID и вот тут столкнемся с первым недостатком COM. GUID непонятен и не читаем ну и все остальное описанное на Wiki. Нам он то же нужен, но в более читаемом и понятном виде (назовем его uiid).

IUnknown и uiid

#define define_uiid(name) \ 	inline static const std::string& guid() { const static std::string idn(dom_guid_pre_name #name); return idn; }  namespace Dom { 	using uiid = std::string; 	using clsuid= std::string;  	struct IUnknown 	{ 		virtual long AddRef() = 0; 		virtual long Release() = 0; 		virtual bool QueryInterface(const uiid&, void **ppv) = 0; 		define_uiid(Unknown) 	}; }

Помимо идентификатора интерфейса, выделяется и идентификатор класса (clsuid), необходимый для создания объекта. В нашем случае, т.к. это более менее читаемый идентификатор, который может определять суть, можно пока забыть о их публикации (возможно это не хорошо).

Резюме
COM-объект, содержит единственный идентификатор класса. Реализует как минимум один COM-интерфейс — IUnknown (любой COM-интерфейс имеет уникальный идентификатор интерфейса). Разные реализации COM-объекта могут иметь один и тот же идентификатор класса (пример: release и debug версия).

COM-сервер (2)

Динамически подключаемой библиотека (для Linux это Shared object — so) реализующая как минимум один COM-объект. Сервер должен экспортировать определенный набор функций:

extern "C"  bool DllCreateInstance(const uiid& iid, void** ppv)

Создает объект класса по clsuid, увеличивает количество ссылок на so, каждый раз при успешном создании объекта. Вызов IUnknown::AddRef, так же должен увеличивать счетчик ссылок на so, а IUnknown::Release должен уменьшать.

extern "C"  bool DllCanUnloadNow()

Если количество ссылок на SO равно 0, то можно выгружать библиотеку.

extern "C"  bool DllRegisterServer(IUnknown* unknown)

Регистрирует в “реестре” все clsuid сервера. Вызывается единожды при инсталляции COM-сервера.

extern "C"  bool DllUnRegisterServer(IUnknown* unknown)

Удаляет из “реестра” записи о зарегистрированных clsuid сервера. Вызывается единожды при деинсталляции COM-сервера.

Пример SimpleHello, объявляем интерфейс IHello:

struct IHello : public virtual Dom::IUnknown { 	virtual void Print() = 0; 	define_uiid(Hello) }; 

Реализация интерфейса:

 /* COM-объект */ class SimpleHello : public Dom::Implement<SimpleHello, IHello> { public: 	SimpleHello() { printf("%s\n", __PRETTY_FUNCTION__); } 	~SimpleHello() { printf("%s\n", __PRETTY_FUNCTION__); } 	virtual void Print() { 		printf("Hello from %s\n",__PRETTY_FUNCTION__); 	} 	define_clsuid(SimpleHello) };  /* COM-сервер */ namespace Dom {  	DOM_SERVER_EXPORT_BEGIN 		EXPORT_CLASS(SimpleHello) 	DOM_SERVER_EXPORT_END  	DOM_SERVER_INSTALL(IUnknown* unknown) { 		Interface<IRegistryServer> registry; 		if (unknown->QueryInterface(IRegistryServer::guid(), registry)) { // Дополнительные действия при инсталляции сервера 		} 		return true; 	}  	DOM_SERVER_UNINSTALL(IUnknown* unknown) { 		Interface<IRegistryServer> registry; 		if (unknown->QueryInterface(IRegistryServer::guid(), registry)) { // Дополнительные действия прии деинсталляции сервера 		} 		return true; 	} } 

Набор макросов скрывает реализации функций, предоставляя более структурированное объявление и логику.

Dom::Implement<SimpleHello, IHello> — скрывает реализацию методов интерфейса IUnknown, добавляет “сахарок”, при объявлении интерфейсов реализуемых объектом (С++11 и variadic templates):

 template <typename T, typename ... IFACES> 	struct Implement : virtual public IUnknown, virtual public IFACES… { ... }; 

Интерфейс IRegistryServer — определяет набор методов работы с “реестром” COM-серверов.

“Реестр” COM-серверов (3)

Важность реестра можно недооценить, но он является наверное главным столпом COM. Microsoft пишет в системный реестр, создает сложную структуру описания интерфейсов и их атрибутов (idl), я пошел немного по другому пути.

В реализации реестр базируется на файловой системе.
Какие плюшки? Понятность, простота, возможность восстановления, особая плюшка при регистрации сервера можно задать некого рода namespace (директорию относительно базового реестра в которой будет регистрироваться объекты сервера), тем самым можно реализовать целостность и версионность приложений использующих технологию.

Из недостатков, возможные проблемы с безопасностью, подмена реализаций объектов.

Как использовать, пример приложения (4)

Для того чтобы заставить все работать потребуется еще небольшая “библиотечка” и небольшая “программка”.

“Библиотечка” — ни что иное как обертка реализующая и собирающая все в единое целое, работу с реестром, загрузку\выгрузку SO, создание объектов.
Она единственная должна быть указана при сборке приложения. Все остальное, “хочется верить”, она сделает сама.

“Программка” — regsrv — собственно это аналог программы Microsoft RegSrv32, выполняющей те же действия (+ возможность указания namespace, + возможность получения списка зарегистрированных clsuid и COM-серверов).

sample

 #include "../include/dom.h"  #include "../../skel/ihello.h"  int main() { 	Dom::Interface<Dom::IUnknown>	unkwn; 	Dom::Interface<IHello>		hello;  	if (Dom::CreateInstance(Dom::clsid("SimpleHello"), unkwn)) { 		unkwn->QueryInterface(IHello::guid(), hello); 		hello->Print(); 	} 	else { 		printf("[WARNING] Class `SimpleHello` not register.\nFirst execute command\n\tregsrv <fullpath>/libskel.so\n... and try again."); 	}  	return 0; } 

Dom (5)

Dom (Dynamic Object Model), моя реализация для Linux.

git clone

Спасибо.


ссылка на оригинал статьи https://habr.com/post/427919/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *