将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对象的不同实现可以具有相同的类标识符(例如:发行版和调试版)。



COM服务器(2)

一个动态链接的库(对于Linux,它是一个Shared对象-因此),它实现至少一个COM对象。 服务器必须导出一组特定的功能:


 extern "C" bool DllCreateInstance(const uiid& iid, void** ppv) 
每次成功创建对象时,通过clsuid创建一个类对象,并增加对此的引用数。 对IUnknown :: AddRef的调用也应为此增加引用计数,并且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 ::实现<SimpleHello,IHello>-隐藏IUnknown接口方法的实现,在声明由对象(C ++ 11和可变参数模板)实现的接口时添加“糖”:



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

IRegistryServer接口-定义了一组用于处理COM服务器的“注册表”的方法。


COM服务器的“注册” (3)

注册表的重要性可以低估,但它可能是COM的主要支柱。 微软写了注册表,创建了一个复杂的结构来描述接口及其属性(idl),我采用了另一种方式。


在实现中,注册表基于文件系统。
什么样的of头? 了解,简单,恢复的可能性,注册服务器时使用的特殊包子,可以设置某种命名空间(相对于将注册服务器对象的基本注册表的目录),从而可以使用该技术实现应用程序的完整性和版本控制。


缺点,可能的安全问题,对象实现的替代。


如何使用,样本应用程序(4)

为了使一切正常工作,您将需要一个小的“库”和一个小的“程序”。


“库”无非是一个包装器,可将所有内容实现并收集到一个整体中,与注册表一起使用,加载/卸载SO,创建对象。
这是构建应用程序时必须指定的唯一选项。 她会做的所有其他事情,“我想相信”。


Programka -regsrv实际上是Microsoft RegSrv32程序的类似物,该程序执行相同的操作(+可以指定名称空间,+可以获取已注册的clsuid和COM服务器列表)。



样本

 #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; } 

大教堂(5)

Dom(动态对象模型),我在Linux上的实现。

git克隆


谢谢啦

Source: https://habr.com/ru/post/zh-CN427919/


All Articles