获得COM组建接口的几种方法总结(不包含自动化)
声明:下面的内容和代码均改自于杨老师写的“COM 组件设计与应用”。
先写出测试用组件的接口描述
[ object, uuid(9C0330E2-D53F-43E3-B899-97B94BF76B64), helpstring("IFun 范例接口"), pointer_default(unique) ] interface IFun : IUnknown { [helpstring("Add 加法")] HRESULT Add([in] LONG n1, [in] LONG n2, [out,retval] LONG* pVal); }; [ uuid(A7F9A697-9F48-41AE-A697-3A9BE77582CC), version(1.0), helpstring("Simple 1.0 类型库") ] library Simple2Lib { importlib("stdole2.tlb"); [ uuid(CA61C492-9AD3-469F-B75A-C021E03F21FB), helpstring("Fun Class") ] coclass Fun { [default] interface IFun; }; };
下面是GUID的声明
MIDL_DEFINE_GUID(IID, IID_IFun,0x9C0330E2,0xD53F,0x43E3,0xB8,0x99,0x97,0xB9,0x4B,0xF7,0x6B,0x64); MIDL_DEFINE_GUID(IID, LIBID_SimpleLib,0xA7F9A697,0x9F48,0x41AE,0xA6,0x97,0x3A,0x9B,0xE7,0x75,0x82,0xCC); MIDL_DEFINE_GUID(CLSID, CLSID_Fun,0xCA61C492,0x9AD3,0x469F,0xB7,0x5A,0xC0,0x21,0xE0,0x3F,0x21,0xFB);
现在开始进入正题:
一、使用COM声明的最基本的方法
#include "..\Simple\simple2.h" #include "..\Simple\Simple2_i.c" ::CoInitialize( NULL ); IUnknown* pUnk = NULL; IFun* pFun = NULL; HRESULT hr; try { hr = ::CoCreateInstance( //获得指向IUnknow接口的指针 CLSID_Fun, NULL, CLSCTX_INPROC_SERVER, // 以进程内组件 DLL 方式加载 IID_IUnknown, // 想要取得 IUnknown 接口指针 (LPVOID *) &pUnk); if( FAILED( hr ) ) throw( _T("没注册吧?") ); hr = pUnk->QueryInterface( // 从 IUnknown 得到其它接口指针 IID_IFun, // 想要取得 IFun 接口指针 (LPVOID *)&pFun ); if( FAILED( hr ) ) throw( _T("居然没有接口?") ); long nSum; hr = pFun->Add( 1, 2, &nSum ); // IFun::Add() if( SUCCEEDED( hr ) ) { CString sMsg; sMsg.Format( _T("1 + 2 = %d"), nSum ); AfxMessageBox( sMsg ); } BSTR s1 = ::SysAllocString( L"Hello" ); BSTR s2 = ::SysAllocString( L" world" ); BSTR s3 = NULL; hr = pFun->Cat( s1, s2, &s3 ); // IFun::Cat() if( SUCCEEDED( hr ) ) { CString sMsg( s3 ); AfxMessageBox( sMsg ); } // IFun::Cat() 最后一个参数是 [out] 方向属性,因此需要调用者释放 if( s3 ) ::SysFreeString( s3 ); } catch( LPCTSTR lpErr ) { AfxMessageBox( lpErr ); } if( pUnk ) pUnk->Release(); if( pFun ) pFun->Release(); ::CoUninitialize();
二.使用智能指针CComPtr<>
首先在程序初始化时调用AfxOleInit()
AfxOleInit();使用时候
// COM 初始化 以 AfxOleInit() 函数调用,放在了 CUse2App::InitInstance() 中了。 CComPtr < IUnknown > spUnk; // 定义 IUnknown 智能指针 CComPtr < IFun > spFun; // 定义 IFun 智能指针 HRESULT hr; try { // 可以用 CLSID 启动组件,也可以用 ProgID hr = spUnk.CoCreateInstance( CLSID_Fun ); if( FAILED( hr ) ) throw( _T("没有注册组件吧?") ); hr = spUnk.QueryInterface( &spFun ); if( FAILED( hr ) ) throw( _T("居然没有接口?") ); long nSum; hr = spFun->Add( 1, 2, &nSum ); if( SUCCEEDED( hr ) ) { CString sMsg; sMsg.Format( _T("1 + 2 = %d"), nSum ); AfxMessageBox( sMsg ); } CComBSTR s1( "Hello" ), s2( " world" ), s3; hr = spFun->Cat( s1, s2, &s3 ); if( SUCCEEDED( hr ) ) { CString sMsg( s3 ); AfxMessageBox( sMsg ); } } catch( LPCTSTR lpErr ) { AfxMessageBox( lpErr ); } // 智能接口指针的最大好处是,我们不用负责释放
三.CComPtr<> 和 CComQIPtr<> 混合的使用方法
CComPtr < IUnknown > spUnk; // 智能指针 IUnknown CComQIPtr < IFun > spFun; // 智能指针 IFun HRESULT hr; try { // 使用 ProgID 启动组件 hr = spUnk.CoCreateInstance( L"Simple2.fun.1" ); if( FAILED( hr ) ) throw( _T("没有注册吧?") ); spFun = spUnk; // CComQIPtr 会帮我们自动调用 QueryInterface if( !spFun ) throw( _T("居然没有接口?") ); // 成功与否可以判断 非NULL long nSum; hr = spFun->Add( 1, 2, &nSum ); if( SUCCEEDED( hr ) ) { CString sMsg; sMsg.Format( _T("1 + 2 = %d"), nSum ); AfxMessageBox( sMsg ); } CComBSTR s1( "Hello" ), s2( " world" ), s3; hr = spFun->Cat( s1, s2, &s3 ); if( SUCCEEDED( hr ) ) { CString sMsg( s3 ); AfxMessageBox( sMsg ); } } catch( LPCTSTR lpErr ) { AfxMessageBox( lpErr ); }
四. CComQIPtr<> 的使用方法
//不必获得IUnknow指针 CComQIPtr < IFun, &IID_IFun > spFun; // 定义 IFun 智能指针 HRESULT hr; try { hr = spFun.CoCreateInstance( L"Simple2.fun.1" ); if( FAILED( hr ) ) throw( _T("没有注册组件 或 没有找到接口") ); long nSum; hr = spFun->Add( 1, 2, &nSum ); if( SUCCEEDED( hr ) ) { CString sMsg; sMsg.Format( _T("1 + 2 = %d"), nSum ); AfxMessageBox( sMsg ); } CComBSTR s1( "Hello" ), s2( " world" ), s3; hr = spFun->Cat( s1, s2, &s3 ); if( SUCCEEDED( hr ) ) { CString sMsg( s3 ); AfxMessageBox( sMsg ); } } catch( LPCTSTR lpErr ) { AfxMessageBox( lpErr ); }
五.智能指针的释放
#include <atlbase.h> #include "..\Simple2\simple2.h" #include "..\Simple2\Simple2_i.c" ::CoInitialize( NULL ); // 如果在这里进行 COM 初始化,要注意智能指针的释放 CComQIPtr < IFun, &IID_IFun > spFun; HRESULT hr = spFun.CoCreateInstance( CLSID_Fun ); ASSERT( SUCCEEDED( hr ) ); // 为了简单起见,不再使用 if 判断 HRESULT 了。IFun::Add() 也没有调用 CComBSTR s1( "Hello" ), s2( " world" ), s3; hr = spFun->Cat( s1, s2, &s3 ); ASSERT( SUCCEEDED( hr ) ); CString sMsg( s3 ); AfxMessageBox( sMsg ); //spFun->Release(); // 大错特错!!! spFun.Release(); // 正解 ::CoUninitialize();
六.包装的智能指针 IxxxPtr、_bstr_t、_variant_t 的使用方法和异常处理
首先在程序初始化时使用AfxOleInit()
AfxOleInit();在包含头文件中加入#import后编译,产生.tlh智能指针包装类。#import 使用了 no_namespace 表示不使用命名空间。智能指针的包装形式是:IxxxPtr,xxx 表示接口名
#ifdef _DEBUG #import "..\Simple2\Debug\Simple2.tlb" no_namespace #else #import "..\Simple2\Release\Simple2.tlb" no_namespace #endif最后实现代码
IFunPtr spFun; HRESULT hr = spFun.CreateInstance( L"Simple2.fun.1" ); // 使用 ProgID //HRESULT hr = spFun.CreateInstance( __uuidof( Fun ) ); // 使用 CLSID ASSERT( SUCCEEDED( hr ) ); try { long nSum = spFun->Add( 1, 2 ); CString sMsg; sMsg.Format( _T("1+2=%d"), nSum ); AfxMessageBox( sMsg ); _bstr_t sCat = spFun->Cat( _T("Hello"), _T(" world") ); AfxMessageBox( sCat ); } catch( _com_error &e ) { // 在这里可以取得详细的错误信息 // 如果支持ISupportErrorInfo 接口 // e.Description(); // e.ErrorMessage(); // e.ErrorInfo(); // ...... AfxMessageBox( _T("Error") ); }
七.包装类中使用命名空间
首先在程序初始化时使用AfxOleInit()
AfxOleInit();在包含头文件中加入#import后编译,这里不是用no_namespace标记
#ifdef _DEBUG #import "..\Simple2\Debug\Simple2.dll" #else #import "..\Simple2\Release\Simple2.dll" #endif最后实现代码
// 命名空间叫 SimpleLib ,这个名称是组件 IDL 文件 Library 指定的 try { // 这次使用智能指针的构造函数启动组件,书写简单。 // 但也有缺点,因为如果失败的话,不知道错误原因 //Simple2Lib::IFunPtr spFun( L"Simple2.fun.1" ); ProgID 方式 Simple2Lib::IFunPtr spFun( __uuidof(Simple2Lib::Fun) );// CLSID 方式 long nSum = spFun->Add( 1, 2 ); CString sMsg; sMsg.Format( _T("1+2=%d"), nSum ); AfxMessageBox( sMsg ); _bstr_t sCat = spFun->Cat( _T("Hello"), _T(" world") ); AfxMessageBox( sCat ); } catch( _com_error &e ) { e; AfxMessageBox( _T("Error") ); }