如何手动构建一个COM

 

简单的说,COM比之DLL的优势在于.给定一个COM,即便程序员不知道COM导出了什么样的符号,他依然可以使用它.而DLL则不能.这就是为什么在WORD里可以引用EXECL电子文档的原因.COM的出现有它的必然性,它的前身就是OLE技术.在本质上说,COM与DLL并无区别,但是在结构上,COM有标准,而DLL则没有.所以,程序员可以根据这些标准使用COM.这里我讲一讲如何手动的构建一个COM.(以Visual C++为例)

1.建立一个空的DLL项目,并定义它的导出文件(*.def)如下:

LIBRARY "TestCom.dll"

EXPORTS
 DllCanUnloadNow     @1 PRIVATE
 DllGetClassObject   @2 PRIVATE
 DllRegisterServer   @3 PRIVATE
 DllUnregisterServer @4 PRIVATE

2.建立idl文件(定义COM的接口,举例如下)

import "oaidl.idl";
import "ocidl.idl";

[
 uuid(FAEAE6B8-67BE-42a4-A318-3256781E945A),
 helpstring("TestCom2 Interface"),
 object,
 pointer_default(unique)
]

interface ITestCom2 : IUnknown
{
 HRESULT Sum([in]int nOp1,[in]int nOp2,[out,retval]int * pret);
};

[
 uuid(CA3B37EB-E44A-49b8-9729-6E9222CAE844),
 version(1.0),
 helpstring("TESTCOM2 1.0 Type Library")
]

library TESTCOM2Lib
{
 importlib("stdole32.tlb");
 importlib("stdole2.tlb");


 [
  uuid(3BCFE27D-C88D-453C-8C94-F5F7B97E7841),
  helpstring("TestCom2 Class")
 ]
 coclass TESTCOM2
 {
  [default] interface ITestCom2;
 };
};

3.建立完idl文件后,编绎这个文件.系统会生成两个.c和一个.h文件,这三个文件是后面程序需要include的,关于interface的头文件

4.实现DllCanUnloadNow   DllGetClassObject    DllRegisterServer   DllUnregisterServer 四个函数,这四个函数是COM向系统注册以及撤消里需要用到的.给出实例如下:

const char * g_RegTable[][3]={
 {"CLSID//{3BCFE27D-C88D-453C-8C94-F5F7B97E7841}",0,"TestCom2"},
 {"CLSID//{3BCFE27D-C88D-453C-8C94-F5F7B97E7841}//InprocServer32",0,(const char * )-1 /*表示文件名的值*/},
 {"CLSID//{3BCFE27D-C88D-453C-8C94-F5F7B97E7841}//ProgID",0,"tulip.TestCom2.1"},
 {"tulip.TestCom2.1",0,"TESTCOM2"},
 {"tulip.TestCom2.1//CLSID",0,"{3BCFE27D-C88D-453C-8C94-F5F7B97E7841}"},
};

HINSTANCE  g_hinstDll;

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
      )
{
 g_hinstDll=(HINSTANCE)hModule;
    return TRUE;
}

STDAPI DllUnregisterServer(void)
{
 HRESULT hr=S_OK;
 char szFileName [MAX_PATH];
 ::GetModuleFileNameA(g_hinstDll,szFileName,MAX_PATH);

 int nEntries=sizeof(g_RegTable)/sizeof(*g_RegTable);
 for(int i =0;SUCCEEDED(hr)&&i<nEntries;i++)
 {
  const char * pszKeyName=g_RegTable[i][0];
  long err=::RegDeleteKeyA(HKEY_CLASSES_ROOT,pszKeyName);
  if(err!=ERROR_SUCCESS)
   hr=S_FALSE;
 }

 return hr;
}

STDAPI DllRegisterServer(void)
{
 HRESULT hr=S_OK;
 char szFileName [MAX_PATH];
 ::GetModuleFileNameA(g_hinstDll,szFileName,MAX_PATH);

 int nEntries=sizeof(g_RegTable)/sizeof(*g_RegTable);
 for(int i =0;SUCCEEDED(hr)&&i<nEntries;i++)
 {
  const char * pszKeyName=g_RegTable[i][0];
  const char * pszValueName=g_RegTable[i][1];
  const char * pszValue=g_RegTable[i][2];

  if(pszValue==(const char *)-1)
  {
   pszValue=szFileName;
  }

  HKEY hkey;
  long err=::RegCreateKeyA(HKEY_CLASSES_ROOT,pszKeyName,&hkey);
  if(err==ERROR_SUCCESS)
  {
   err=::RegSetValueExA(hkey,pszValueName,0,REG_SZ,(const BYTE*)pszValue,(strlen(pszValue)+1));
   ::RegCloseKey(hkey);
  }
  if(err!=ERROR_SUCCESS)
  {
   ::DllUnregisterServer();
   hr=E_FAIL;
  }

 }
   return hr;
}

STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv)
{
 static CTestCom2 *pm=new CTestCom2;
 if(rclsid==CLSID_TESTCOM2)
  return pm->QueryInterface(riid,ppv);

 return CLASS_E_CLASSNOTAVAILABLE;
}

STDAPI DllCanUnloadNow(void)
{
 return S_OK;
}

5.建立C++的类文件.h以及.cpp,实现一个CTestCom继承刚才定义的接口.并实现QueryInterface,AddRef,Release函数,当然,这里面可以包函自定义的工具函数.

 

class CTestCom2 : public ITestCom2
{
private:

 ULONG m_cRef;

public:

 CTestCom2();

 //IUnknown Method
 STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
 STDMETHOD_(ULONG, AddRef)();
 STDMETHOD_(ULONG, Release)();
 
 //function
 STDMETHOD (Sum)(int nOp1, int nOp2,int * pret);

};

STDMETHODIMP CTestCom2::QueryInterface(REFIID riid, void **ppv)
{
 if(riid == IID_ITestCom2)
 {
  *ppv = static_cast<ITestCom2 *>(this);
 }
 else if(riid == IID_IUnknown)
  *ppv = static_cast<ITestCom2 *>(this);
 else
 {
  *ppv = 0;
  return E_NOINTERFACE;
 }

 reinterpret_cast<IUnknown *>(*ppv)->AddRef(); //这里要这样是因为引用计数是针对组件的
 return S_OK;
}

STDMETHODIMP_(ULONG) CTestCom2::AddRef()
{
 return ++m_cRef;
}

STDMETHODIMP_(ULONG) CTestCom2::Release()
{
 ULONG res = --m_cRef; // 使用临时变量把修改后的引用计数值缓存起来
 if(res == 0)   // 因为在对象已经销毁后再引用这个对象的数据将是非法的
  delete this;
 return res;
}

CTestCom2::CTestCom2()
{
 m_cRef = 0;
}

STDMETHODIMP CTestCom2::Sum(int nOp1, int nOp2,int * pret)
{
  *pret=nOp1+nOp2;
  return S_OK;
}

6.定义完上述文件后,可以build了,build成功后,下列代码可以完成COM的调用测试.

HRESULT hr;

 hr = ::CoInitialize(0);

 ITestCom2 *TCom2 = NULL;

 int nReturnValue = 0;

 hr=::CoGetClassObject(CLSID_TESTCOM2,CLSCTX_INPROC,NULL,IID_ITestCom2,(void **)&TCom2);

 //hr=::CoCreateInstance(CLSID_TESTCOM2,NULL,CLSCTX_INPROC,IID_ITestCom2,(void **)&TCom2);

 if(SUCCEEDED(hr))
 {
  hr=TCom2->Sum(55,66,&nReturnValue);
  if(SUCCEEDED(hr))
   cout << "55 + 66 = " <<nReturnValue<< endl;
 }

 TCom2->Release();

 ::CoUninitialize();

 以上只是写了构建一个简单COM的基本步骤,至于为什么这么做,我并没有详细介绍.如果读者能够对COM有一定的了解后,我想了解这个过程将更加容易.

### 3.1 创建 Dockerfile 定义镜像内容 Dockerfile 是一种被 Docker 程序解释的脚本,由一条一条的指令组成,每条指令对应 Linux 下的一条命令。通过编写 Dockerfile,可以明确地定义镜像的构建过程,并确保其可重复性和一致性。例如,一个用于构建 Nginx 镜像的 Dockerfile 可以基于 CentOS,并安装必要的软件包和配置服务: ```dockerfile # This is docker file # version 1.0 # Author: xu chen yang # Base image FROM centos # Maintainer information MAINTAINER chenyang.xu chenyang.xu@enjoymov.com # Install EPEL repository and nginx RUN rpm -ivh https://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm RUN yum -y install nginx # Modify configuration to run in foreground RUN echo "daemon off;" >> /etc/nginx/nginx.conf # Expose port 80 EXPOSE 80 # Start nginx by default CMD ["nginx"] ``` 该文件定义了基础镜像、维护者信息、安装步骤、暴露端口以及默认启动命令[^4]。 ### 3.2 使用 `docker build` 构建镜像 完成 Dockerfile 编写后,使用 `docker build` 命令执行镜像构建。命令中 `-t` 参数用于指定镜像名称和标签,`.` 表示当前目录为 Dockerfile 所在路径: ```bash docker build -t 192.168.35.105:5000/develop/xxx-svc:20200331100100 . ``` Docker 程序会逐层执行 Dockerfile 中的指令,并生成最终的定制化镜像。该过程自动处理指令之间的依赖关系,确保每一步都基于前一步的结果进行叠加[^1]。 ### 3.3 手动导入外部文件系统构建镜像 在某些特殊场景下,如从 ISO 文件手动构建麒麟 V10 基础镜像时,可以通过打包系统文件并使用 `docker import` 导入的方式创建基础镜像。例如: ```bash docker import /tmp/kylin-base.tar kylin:V10-base ``` 这种方式适用于需要从现有文件系统快照创建镜像的情况,但不会保留构建过程细节,不如 Dockerfile 方式透明和可追溯[^3]。 ### 3.4 验证与清理 构建完成后,可通过 `docker images` 查看本地镜像列表,确认新构建的镜像是否成功生成。同时,在构建过程中应确保仅包含必要的系统文件,避免将宿主机的临时数据混入镜像,从而保证镜像的轻量化和安全性[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值