上一篇文章简单演示了COM组件的编写,注册及调用,本篇实现COM的自注册和反注册,定义一个有实际功能的接口及实现IClassFactory接口。
1. 实现COM组件的自注册和反注册
实现COM组件的自注册和反注册,本质上就是写注册表与删注册表。需要在DLL中引出两个函数:DllRegisterServer和DllUnregisterServer,让这两个函数实现注册表操作。
现将需要修改的注册表内容放到一个二维数组中,第一维内容分别是注册表键,项,值。-1为占位符,标识该位置放置COM组件的路径,在写注册表之前将其替换。
删注册表时,应该先删子键,否则删除会失败。
同时需要在BeginningCOM.def中导出这两个函数:
这样就可以用regsvr32.exe注册和反注册COM组件。
2. 自定义接口
可以用IDL文件定义,也可以在代码中直接继承IUnknown接口实现。用IDL定义接口的好处是,MIDL工具能自动生成GUID描述,接口存根/代理等。IDL全称为:Interface Definition Language,其语法类似于C语言,去掉了存在二义性的内容,并进行了扩展。作为示例,我们定义一个IBeginningCOM的接口,里面添加一个Sum函数和一个Num属性。
引入的oaidl.idl等包含了系统标准接口声明等,直接引入即可。接口的object和接口的名字这两个特性是必须的,为防止接口重名,用GUID来表示接口的名字。propget和proppub表示方法映射到属性,如VB中,在不支持属性的语言,如C++,映射为get_XXX,put_XXX。每个IDL文件只能有一个library标识,内部可以有多个coclass。
IDL文件经MIDL工具编译后,生成四个文件:
*_h.h 接口说明文件
*_i.c GUID描述文件
*_p.c 接口存根/代理实现文件
dlldata.c 包含存根/代理相关内容
3. 实现IClassFactory接口
IClassFactory是系统定义的对象创建的标准接口。IClassFactory有两个方法:LockServer和CreateInstance。LockServer用于进程外激活时,COM内部对其进行调用,目前我们都是进程内激活,可先不实现;CreateInstance用于创建请求的类对象,该方法的第一个参数在聚合时使用。
实现IClassFactory:
实现了IClassFactory接口,创建COM对象的代码可以通过查询IClassFactory接口,然后调用该接口的CreateInstance方法:
也可以用CoCreateInstance创建,该函数包装了上面的两布,在分布式环境下能减少一次客户端与服务器之间的通信。
1. 实现COM组件的自注册和反注册
实现COM组件的自注册和反注册,本质上就是写注册表与删注册表。需要在DLL中引出两个函数:DllRegisterServer和DllUnregisterServer,让这两个函数实现注册表操作。
LPCTSTR RegTable[][
3
]
=
{
{L " CLSID\\{586CDC7B-09F1-4f44-A110-F0E604AED81E} " , 0 , L " BeginningCOM " },
{L " CLSID\\{586CDC7B-09F1-4f44-A110-F0E604AED81E}\\InprocServer32 " , 0 , (LPCTSTR) - 1 },
{L " CLSID\\{586CDC7B-09F1-4f44-A110-F0E604AED81E}\\InprocServer32 " , L " ThreadingModel " , L " Both " }
};
STDAPI DllUnregisterServer()
{
HRESULT hr = S_OK;
int regCount = sizeof (RegTable) / sizeof ( * RegTable);
for ( int i = regCount - 1 ; i >= 0 ; i -- )
{
LSTATUS error = RegDeleteKey(HKEY_CLASSES_ROOT, RegTable[i][ 0 ]);
if (error != ERROR_SUCCESS)
{
hr = S_FALSE;
}
}
return hr;
}
STDAPI DllRegisterServer( void )
{
HRESULT hr = S_OK;
TCHAR szFileName[MAX_PATH];
ZeroMemory( & szFileName, MAX_PATH * sizeof (TCHAR));
GetModuleFileName(g_hModule, szFileName, MAX_PATH);
int regCount = sizeof (RegTable) / sizeof ( * RegTable);
for ( int i = 0 ; i < regCount; i ++ )
{
if (RegTable[i][ 2 ] == (LPCTSTR) - 1 )
{
RegTable[i][ 2 ] = szFileName;
}
HKEY hKey;
LSTATUS error = ::RegCreateKey(HKEY_CLASSES_ROOT, RegTable[i][ 0 ], & hKey);
if (error == ERROR_SUCCESS)
{
error = RegSetValueEx(hKey, RegTable[i][ 1 ], 0 , REG_SZ, ( const BYTE * )RegTable[i][ 2 ], (lstrlen(RegTable[i][ 2 ]) + 1 ) * sizeof (TCHAR));
RegCloseKey(hKey);
}
if (error != ERROR_SUCCESS)
{
DllUnregisterServer();
}
}
return hr;
}
{
{L " CLSID\\{586CDC7B-09F1-4f44-A110-F0E604AED81E} " , 0 , L " BeginningCOM " },
{L " CLSID\\{586CDC7B-09F1-4f44-A110-F0E604AED81E}\\InprocServer32 " , 0 , (LPCTSTR) - 1 },
{L " CLSID\\{586CDC7B-09F1-4f44-A110-F0E604AED81E}\\InprocServer32 " , L " ThreadingModel " , L " Both " }
};
STDAPI DllUnregisterServer()
{
HRESULT hr = S_OK;
int regCount = sizeof (RegTable) / sizeof ( * RegTable);
for ( int i = regCount - 1 ; i >= 0 ; i -- )
{
LSTATUS error = RegDeleteKey(HKEY_CLASSES_ROOT, RegTable[i][ 0 ]);
if (error != ERROR_SUCCESS)
{
hr = S_FALSE;
}
}
return hr;
}
STDAPI DllRegisterServer( void )
{
HRESULT hr = S_OK;
TCHAR szFileName[MAX_PATH];
ZeroMemory( & szFileName, MAX_PATH * sizeof (TCHAR));
GetModuleFileName(g_hModule, szFileName, MAX_PATH);
int regCount = sizeof (RegTable) / sizeof ( * RegTable);
for ( int i = 0 ; i < regCount; i ++ )
{
if (RegTable[i][ 2 ] == (LPCTSTR) - 1 )
{
RegTable[i][ 2 ] = szFileName;
}
HKEY hKey;
LSTATUS error = ::RegCreateKey(HKEY_CLASSES_ROOT, RegTable[i][ 0 ], & hKey);
if (error == ERROR_SUCCESS)
{
error = RegSetValueEx(hKey, RegTable[i][ 1 ], 0 , REG_SZ, ( const BYTE * )RegTable[i][ 2 ], (lstrlen(RegTable[i][ 2 ]) + 1 ) * sizeof (TCHAR));
RegCloseKey(hKey);
}
if (error != ERROR_SUCCESS)
{
DllUnregisterServer();
}
}
return hr;
}
现将需要修改的注册表内容放到一个二维数组中,第一维内容分别是注册表键,项,值。-1为占位符,标识该位置放置COM组件的路径,在写注册表之前将其替换。
删注册表时,应该先删子键,否则删除会失败。
同时需要在BeginningCOM.def中导出这两个函数:
LIBRARY
"
BeginningCOM
"
EXPORTS
DllGetClassObject private
DllRegisterServer private
DllUnregisterServer private
EXPORTS
DllGetClassObject private
DllRegisterServer private
DllUnregisterServer private
这样就可以用regsvr32.exe注册和反注册COM组件。
2. 自定义接口
可以用IDL文件定义,也可以在代码中直接继承IUnknown接口实现。用IDL定义接口的好处是,MIDL工具能自动生成GUID描述,接口存根/代理等。IDL全称为:Interface Definition Language,其语法类似于C语言,去掉了存在二义性的内容,并进行了扩展。作为示例,我们定义一个IBeginningCOM的接口,里面添加一个Sum函数和一个Num属性。
import
"
oaidl.idl
"
;
import " ocidl.idl " ;
[ object , uuid(93C3840F - AD5A - 4020 - AAAB - 313C4B61B184)]
interface IBeginningCOM : IUnknown
{
HRESULT Sum([ in ] int a, [ in ] int b, [ out , retval] int * sum);
[propget] HRESULT Num([ out , retval] int * pVal);
[propput] HRESULT Num([ in ] int val);
}
[
uuid(D9161D4D - 66C0 - 4ae6 - 9264 - C322BDE034C7),
version( 1.0 ),
helpstring( " BeginningCOMLib " )
]
library BEGINNINGCOMLib
{
importlib( " stdole32.tlb " );
importlib( " stdole2.tlb " );
[
uuid(586CDC7B - 09F1 - 4f44 - A110 - F0E604AED81E),
helpstring( " BeginningCOM Lib " )
]
coclass BeginningCOM
{
[ default ] interface IBeginningCOM;
};
};
import " ocidl.idl " ;
[ object , uuid(93C3840F - AD5A - 4020 - AAAB - 313C4B61B184)]
interface IBeginningCOM : IUnknown
{
HRESULT Sum([ in ] int a, [ in ] int b, [ out , retval] int * sum);
[propget] HRESULT Num([ out , retval] int * pVal);
[propput] HRESULT Num([ in ] int val);
}
[
uuid(D9161D4D - 66C0 - 4ae6 - 9264 - C322BDE034C7),
version( 1.0 ),
helpstring( " BeginningCOMLib " )
]
library BEGINNINGCOMLib
{
importlib( " stdole32.tlb " );
importlib( " stdole2.tlb " );
[
uuid(586CDC7B - 09F1 - 4f44 - A110 - F0E604AED81E),
helpstring( " BeginningCOM Lib " )
]
coclass BeginningCOM
{
[ default ] interface IBeginningCOM;
};
};
引入的oaidl.idl等包含了系统标准接口声明等,直接引入即可。接口的object和接口的名字这两个特性是必须的,为防止接口重名,用GUID来表示接口的名字。propget和proppub表示方法映射到属性,如VB中,在不支持属性的语言,如C++,映射为get_XXX,put_XXX。每个IDL文件只能有一个library标识,内部可以有多个coclass。
IDL文件经MIDL工具编译后,生成四个文件:
*_h.h 接口说明文件
*_i.c GUID描述文件
*_p.c 接口存根/代理实现文件
dlldata.c 包含存根/代理相关内容
//
BeginningCOM继承自IBeginningCOM接口
// ...
class BeginningCOM : public IBeginningCOM
// ...
// 实现IBeginningCOM接口成员,属性需要分别实现set/put。
// ...
STDMETHODIMP BeginningCOM::Sum( int a, int b, int * sum)
{
* sum = a + b;
return S_OK;
}
STDMETHODIMP BeginningCOM::get_Num( int * pVal)
{
* pVal = m_Num;
return S_OK;
}
STDMETHODIMP BeginningCOM::put_Num( int val)
{
m_Num = val;
return S_OK;
}
// ...
class BeginningCOM : public IBeginningCOM
// ...
// 实现IBeginningCOM接口成员,属性需要分别实现set/put。
// ...
STDMETHODIMP BeginningCOM::Sum( int a, int b, int * sum)
{
* sum = a + b;
return S_OK;
}
STDMETHODIMP BeginningCOM::get_Num( int * pVal)
{
* pVal = m_Num;
return S_OK;
}
STDMETHODIMP BeginningCOM::put_Num( int val)
{
m_Num = val;
return S_OK;
}
3. 实现IClassFactory接口
IClassFactory是系统定义的对象创建的标准接口。IClassFactory有两个方法:LockServer和CreateInstance。LockServer用于进程外激活时,COM内部对其进行调用,目前我们都是进程内激活,可先不实现;CreateInstance用于创建请求的类对象,该方法的第一个参数在聚合时使用。
实现IClassFactory:
#pragma
once
#include " unknwn.h "
class BeginningCOMClassFactory :
public IClassFactory
{
public :
BeginningCOMClassFactory(VOID); // IUknown
STDMETHODIMP QueryInterface(REFIID riid, VOID ** ppv);
STDMETHODIMP_(ULONG) AddRef(VOID);
STDMETHODIMP_(ULONG) Release(VOID);
// IClassFactory
STDMETHODIMP CreateInstance(LPUNKNOWN punkOuter, REFIID riid, VOID ** ppv);
STDMETHODIMP LockServer(BOOL fLock);
protected :
ULONG m_ulRefCount;
};
// ...
STDMETHODIMP BeginningCOMClassFactory::CreateInstance(LPUNKNOWN punkOuter, REFIID riid, VOID ** ppv)
{
BeginningCOM * pbc;
HRESULT hr;
// 暂不支持聚合
if (punkOuter != NULL)
{
return CLASS_E_NOAGGREGATION;
}
pbc = new BeginningCOM;
if (pbc == NULL)
{
return E_OUTOFMEMORY;
}
pbc -> AddRef();
hr = pbc -> QueryInterface(riid, ppv);
pbc -> Release();
return hr;
}
STDMETHODIMP BeginningCOMClassFactory::LockServer(BOOL fLock)
{
return E_FAIL;
}
DllGetClassObject返回是否实现IClassFactory即可。
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppv)
{
if (rclsid == CLSID_BeginningCOM)
{
BeginningCOMClassFactory * pbc = new BeginningCOMClassFactory;
if (pbc == NULL)
{
return E_OUTOFMEMORY;
}
return pbc -> QueryInterface(riid, ppv);
}
* ppv = 0 ;
return CLASS_E_CLASSNOTAVAILABLE;
}
#include " unknwn.h "
class BeginningCOMClassFactory :
public IClassFactory
{
public :
BeginningCOMClassFactory(VOID); // IUknown
STDMETHODIMP QueryInterface(REFIID riid, VOID ** ppv);
STDMETHODIMP_(ULONG) AddRef(VOID);
STDMETHODIMP_(ULONG) Release(VOID);
// IClassFactory
STDMETHODIMP CreateInstance(LPUNKNOWN punkOuter, REFIID riid, VOID ** ppv);
STDMETHODIMP LockServer(BOOL fLock);
protected :
ULONG m_ulRefCount;
};
// ...
STDMETHODIMP BeginningCOMClassFactory::CreateInstance(LPUNKNOWN punkOuter, REFIID riid, VOID ** ppv)
{
BeginningCOM * pbc;
HRESULT hr;
// 暂不支持聚合
if (punkOuter != NULL)
{
return CLASS_E_NOAGGREGATION;
}
pbc = new BeginningCOM;
if (pbc == NULL)
{
return E_OUTOFMEMORY;
}
pbc -> AddRef();
hr = pbc -> QueryInterface(riid, ppv);
pbc -> Release();
return hr;
}
STDMETHODIMP BeginningCOMClassFactory::LockServer(BOOL fLock)
{
return E_FAIL;
}
DllGetClassObject返回是否实现IClassFactory即可。
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppv)
{
if (rclsid == CLSID_BeginningCOM)
{
BeginningCOMClassFactory * pbc = new BeginningCOMClassFactory;
if (pbc == NULL)
{
return E_OUTOFMEMORY;
}
return pbc -> QueryInterface(riid, ppv);
}
* ppv = 0 ;
return CLASS_E_CLASSNOTAVAILABLE;
}
实现了IClassFactory接口,创建COM对象的代码可以通过查询IClassFactory接口,然后调用该接口的CreateInstance方法:
HRESULT hr
=
NULL;
IClassFactory * pcf;
IBeginningCOM * pbc;
hr = CoGetClassObject(CLSID_BeginningCOM, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, ( void ** ) & pcf);
if (SUCCEEDED(hr))
{
hr = pcf -> CreateInstance(NULL, IID_IBeginningCOM, ( void ** ) & pbc);
// ...
IClassFactory * pcf;
IBeginningCOM * pbc;
hr = CoGetClassObject(CLSID_BeginningCOM, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, ( void ** ) & pcf);
if (SUCCEEDED(hr))
{
hr = pcf -> CreateInstance(NULL, IID_IBeginningCOM, ( void ** ) & pbc);
// ...
也可以用CoCreateInstance创建,该函数包装了上面的两布,在分布式环境下能减少一次客户端与服务器之间的通信。
//
...
hr = CoCreateInstance(CLSID_BeginningCOM, NULL, CLSCTX_INPROC_SERVER, IID_IBeginningCOM, ( void ** ) & pbc);
// ...
hr = CoCreateInstance(CLSID_BeginningCOM, NULL, CLSCTX_INPROC_SERVER, IID_IBeginningCOM, ( void ** ) & pbc);
// ...
源程序下载:BeginningCOM2.zip
http://www.cnblogs.com/zxjay/archive/2010/08/28/1811655.html