27 октября 2018

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

Open sourceC++Разработка под 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


Спасибо.
Теги:c++11COMtechnologylinux
Хабы: Open source C++ Разработка под Linux
+21
12k 60
Комментарии 38
Лучшие публикации за сутки

Минуточку внимания

Разместить