Saturday, June 2, 2007

Tips of COM technique


COM 初始化:


方法一:


::CoInitialize( NULL );
... ...


::CoUninitialize();


方法二:


AfxOleInit();

Client获取COM组件相应接口的三种方式:


方法一:直接#include发布的两个头文件:


#include "ComServer.h" // 着两个文件是由idl 编译生成的,里面有interface的声明
#include "ComServer_i.c"


方法二:#import dll 或tlb:


#import "ComServer.dll" no_namespace; //编译后会生成ComServer.tlh 和 ComServer.tli文件
#import "ComServer.tlb"


方法三:直接copy ComServer.tlh和ComServer.tli到project中,然后include .tlh文件


#include "ComServer.tlh"


idl,tlb,tlh,tli


.idl => .h, _i.c, .tlb
.tlb =>.tlh, .tli


普通COM接口调用方法:


方法一:


::CoInitialize( NULL );
IUnknown * pUnk = NULL;
IFun * pFun = NULL;
hr = ::CoCreateInstance(
CLSID_Fun,
NULL,
CLSCTX_INPROC_SERVER, // 以进程内组件 DLL 方式加载
IID_IUnknown, // 想要取得 IUnknown 接口指针
(LPVOID *) &pUnk);

hr = pUnk->QueryInterface( // 从 IUnknown 得到其它接口指针
IID_IFun, // 想要取得 IFun 接口指针
(LPVOID *)&pFun );
pFun->Do( );


if( pUnk ) pUnk->Release();
if( pFun ) pFun->Release();


::CoUninitialize();


方法二:


CComPtr < IUnknown > spUnk; // 定义 IUnknown 智能指针
CComPtr < IFun > spFun; // 定义 IFun 智能指针
//用 CLSID 启动组件
hr = spUnk.CoCreateInstance( CLSID_Fun );
hr = spUnk.QueryInterface( &spFun );
spFun->Do( );


或不通过IUnknow,直接create:


hr = spFun .CoCreateInstance( CLSID_Fun );
spFun->Do( );

方法三:


CComPtr < IUnknown > spUnk; // 智能指针 IUnknown
CComQIPtr < IFun > spFun; // 智能指针 IFun
// 使用 ProgID 启动组件
hr = spUnk.CoCreateInstance( L"Simple2.fun.1" );
spFun = spUnk; // CComQIPtr 会帮我们自动调用 QueryInterface
spFun->Do( );


方法四:


// 不再经过 IUnknown
CComQIPtr < IFun, &IID_IFun > spFun; // 定义 IFun 智能指针
hr = spFun.CoCreateInstance( L"Simple2.fun.1" );
spFun->Do( );


方法五:


//#import 的方式,编译后产生 .tlh 和 .tlh 的智能指针包装,其包装形式是:IxxxPtr,xxx 表示接口名。
IFunPtr spFun; //智能指针包装
HRESULT hr = spFun.CreateInstance( L"Simple2.fun.1" ); // 使用 ProgID
// HRESULT hr = spFun.CreateInstance( __uuidof( Fun ) ); // 使用 CLSID
spFun->Do( );


方法六:


// 同方法五,但这次使用智能指针的构造函数启动组件,书写简单。
// 但也有缺点,因为如果失败的话,不知道错误原因
//IFunPtr spFun( L"Simple2.fun.1" ); // ProgID 方式
IFunPtr spFun( __uuidof(Fun) ); // CLSID 方式
spFun->Do( );


IDispatch接口调用方法:


方法一:原始的调用方法


void Func{


::CoInitialize( NULL ); // COM 初始化


CLSID clsid; // 通过 ProgID 得到 CLSID
HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );
ASSERT( SUCCEEDED( hr ) ); // 如果失败,说明没有注册组件


IDispatch * pDisp = NULL; // 由 CLSID 启动组件,并得到 IDispatch 指针
hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp );
ASSERT( SUCCEEDED( hr ) ); // 如果失败,说明没有初始化 COM


LPOLESTR pwFunName = L"Add"; // 准备取得 Add 函数的序号 DispID
DISPID dispID; // 取得的序号,准备保存到这里
hr = pDisp->GetIDsOfNames( // 根据函数名,取得序号的函数
IID_NULL,
&pwFunName, // 函数名称的数组
1, // 函数名称数组中的元素个数
LOCALE_SYSTEM_DEFAULT, // 使用系统默认的语言环境
&dispID ); // 返回值
ASSERT( SUCCEEDED( hr ) ); // 如果失败,说明组件根本就没有 ADD 函数


VARIANTARG v[2]; // 调用 Add(1,2) 函数所需要的参数
v[0].vt = VT_I4; v[0].lVal = 2; // 第二个参数,整数2
v[1].vt = VT_I4; v[1].lVal = 1; // 第一个参数,整数1


DISPPARAMS dispParams = { v, NULL, 2, 0 }; // 把参数包装在这个结构中
VARIANT vResult; // 函数返回的计算结果


hr = pDisp->Invoke( // 调用函数
dispID, // 函数由 dispID 指定
IID_NULL,
LOCALE_SYSTEM_DEFAULT, // 使用系统默认的语言环境
DISPATCH_METHOD, // 调用的是方法,不是属性
&dispParams, // 参数
&vResult, // 返回值
NULL, // 不考虑异常处理
NULL); // 不考虑错误处理
ASSERT( SUCCEEDED( hr ) ); // 如果失败,说明参数传递错误


CString str; // 显示一下结果
str.Format("1 + 2 = %d", vResult.lVal );
AfxMessageBox( str );


pDisp->Release(); // 释放接口指针
::CoUninitialize(); // 释放 COM


}


方法二:CComDispatchDriver


void CUse2Dlg::OnBnClickedOk()
{
// 在 App 类,InitInstance 中,已经调用 AfxOleInit() 进行了 COM 初始化


CLSID clsid; // 通过 ProgID 取得组件的 CLSID
HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );
ASSERT( SUCCEEDED( hr ) ); // 如果失败,说明没有注册组件


CComPtr < IUnknown > spUnk; // 由 CLSID 启动组件,并取得 IUnknown 指针
hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID *)&spUnk );
ASSERT( SUCCEEDED( hr ) );


CComDispatchDriver spDisp( spUnk ); // 构造只能指针
CComVariant v1(1), v2(2), vResult; // 参数
hr = spDisp.Invoke2( // 调用2个参数的函数
L"Add", // 函数名是 Add
&v1, // 第一个参数,值为整数1
&v2, // 第二个参数,值为整数2
&vResult); // 返回值
ASSERT( SUCCEEDED( hr ) ); // 如果失败,说明或者没有 ADD 函数,或者参数错误


CString str; // 显示一下结果
str.Format("1 + 2 = %d", vResult.lVal );
AfxMessageBox( str );


// spUnk 和 spDisp 都是智能指针,会自动释放
// 如果使用 CoInitialize(NULL) 初始化,则必须在 CoUninitialize() 之前
// 调用 spUnk.Release() 和 spDisp.Release() 释放
}


方法三:MFC Class from TypeLib,但它本质上是使用 IDispatch 接口,所以执行效率稍差


// 使用添加"类型库中的 MFC 类"的方式调用自动化接口
// 选择组件后,会产生相应的包装类,于是下面就可以使用了
#include "CDispSimple.h"


void CUse3Dlg::OnBnClickedOk()
{
// COM 初始化,在 App 的InitInstance() 中调用了 AfxOleInit()


CDispSimple spDisp;
if( !spDisp.CreateDispatch( _T("Simple8.DispSimple.1") ) ) // 启动组件
{
AfxMessageBox( _T("启动失败。组件注册了吗?COM 初始化了吗?") );
return ;
}
CString str = spDisp.Upper( _T("hello") ); // 调用转换为大写的函数
AfxMessageBox( str );


spDisp.ReleaseDispatch();
}


方法四:使用 #import 方式使用自动化接口,对双接口组件,直接调用自定义接口函数,不再经过 IDispatch,因此执行效率最高。


#import "..\Simple8.dll" no_namespace
void CUse4Dlg::OnBnClickedOk()
{
// COM 初始化在 App 的 InitInstance() 函数中调用 AfxOleInit()


try
{
// 通过构造函数启动组件
IDispSimplePtr spDisp( _T("Simple8.DispSimple.1") );


// 调用转化为大写的函数
_bstr_t str = spDisp->Upper( _T("hello") );


// 显示一下结果
AfxMessageBox( str );
}
catch(_com_error &e)
{
AfxMessageBox( e.ErrorMessage() );
}
}


智能指针的释放:


::CoInitialize( NULL ); // 如果在这里进行 COM 初始化,要注意智能指针的释放
CComQIPtr < IFun, &IID_IFun > spFun;
hr = spFun.CoCreateInstance( CLSID_Fun );
spFun->Do( );
// spFun->Release(); // 大错特错!!!
spFun.Release(); // 正解
::CoUninitialize();


Compiler COM Support Classes




















_bstr_t



Wraps the BSTR type to provide useful operators and methods.



_com_error



Defines the error object thrown by _com_raise_error in most failures.



_com_ptr_t



Encapsulates COM interface pointers, and automates the required calls to AddRef, Release, and QueryInterface.



_variant_t



Wraps the VARIANT type to provide useful operators and methods.



BSTR,_bstr_t, CComBSTR(ATL), CString(MFC)


// IFun::Cat() 最后一个参数是 [out] 方向属性,因此需要调用者释放
BSTR s1 = ::SysAllocString( L"Hello" );
BSTR s2 = ::SysAllocString( L" world" );
BSTR s3 = NULL;
hr = pFun->Cat( s1, s2, &s3 ); // IFun::Cat()
if( s3 ) ::SysFreeString( s3 );


CComBSTR s1( "Hello" ); // 不再使用 API 方式操作 BSTR
CComBSTR s2( " world" ); // 使用 CComBSTR 比较简单,并且
CComBSTR s3; // 最大的好处是,不用咱们自己来释放
hr = pFun->Cat( s1, s2, &s3 );


VARIANT, _variant_t,CComVariant(ATL), COleVariant(MFC)


...



通过 ProgID 得到 CLSID


CLSID clsid;
HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );
__uuidof(CFun)



AddRef()的几个原则:


1、启动组件得到一个接口指针(Interface)后,不要调用AddRef()。因为系统知道你得到了一个指针,所以它已经帮你调用了AddRef()函数;
  2、通过QueryInterface()得到另一个接口指针后,不要调用AddRef()。因为......和上面的道理一样;
  3、当你把接口指针赋值给(保存到)另一个变量中的时候,请调用AddRef();
  4、当不需要再使用接口指针的时候,务必执行Release()释放;
  5、当使用智能指针的时候,可以省略指针的维护工作;



No comments: