简单的说,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有一定的了解后,我想了解这个过程将更加容易.