如何手动构建一个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有一定的了解后,我想了解这个过程将更加容易.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值