Мне нравится технология 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("%sn", __PRETTY_FUNCTION__); }
~SimpleHello() { printf("%sn", __PRETTY_FUNCTION__); }
virtual void Print() {
printf("Hello from %sn",__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 commandntregsrv <fullpath>/libskel.son... and try again.");
}
return 0;
}
Dom (5)
Dom (Dynamic Object Model), моя реализация для Linux.
Спасибо.
Автор: Zekori