上一篇说道了使用DLL的方式实现混合使用,但是使用过程还有一些复杂,比如VB用使用字符串的一些操作就不得不使用了lstrlen这个API来完成.而且DLL的使用范围也不够广泛,而这次介绍如何使用COM方式进行多语言间调用,相对DLL方式这个容易使用.
按照惯例先使用C++的方式编写(我是使用VS2005)
先新建一个ATL工程,名字为ComCore点确定之后点完成,使用默认的工程配置.再添加一个类选择ATL Simple Object名字输入CComTest
点options,选上ConnectionPoints
点完成,就能看到向导生成的代码了.
切换到类视图(Class View) 之后选中IComTest右键,选择添加方法(Add Method).
输入函数名字参数类型点添加再点完成,只此已经成功添加一个方法
第二个方法添加如下,在选中参数类型的时候,将out选项选上.
第三个方法如下,除了将out选项选中,还将retval选项选中.
接下来在类视图里面选中_ICComTestEvents(在ComCoreLib中),右键选择添加方法,增加一个有两个参数的函数.
接下来选中CCComTest右键,添加->添加连接点(Add Connections Point)
将_ICComTestEvents这个增加到右边,如上图.点击完成
至此,我们已经将所需要的接口都添加上去.点编译即可.但我们只是提供了函数的接口,并没有实现,现在将实现添加上去.打开CComTest.cpp文件就会看到三个函数的空实现(我删掉了注释)
{
return S_OK;
}
STDMETHODIMP CCComTest::Two(VARIANT* p_var )
{
return S_OK;
}
STDMETHODIMP CCComTest::Three(BSTR* p_ret )
{
return S_OK;
}
我们添加合适的代码上去先到CComTest.h给CCComTest增加一个私有的成员变量long len;在构造函数中初始化为0
#include < comutil.h >
#pragma comment(lib,"comsuppw.lib")
// CCComTest
STDMETHODIMP CCComTest::OneFunction(VARIANT p_array)
{
this -> Fire_EventOne( _bstr_t( " 回调的哦 " ) , p_array );//调用回调函数
if ( (VT_INT == p_array.vt) | (VT_I4 == p_array.vt) ) {//得到参数的内容,这里使用VARIANT ,当然也可以直接使用long类型来做参数
len = p_array.lVal;
return S_OK;
} else
return S_FALSE;
}
STDMETHODIMP CCComTest::Two(VARIANT * p_var)
{
SAFEARRAYBOUND bound[ 1 ] = { len };//传递一个数组,这里使用了SafeArray
CComSafeArray < BYTE > x( bound );
for ( int i = 0 ;i < len;i ++ ){
x[i] = i;
}
p_var -> vt = VT_ARRAY | VT_UI1;
x.CopyTo( & (p_var -> parray) );
return S_OK;
}
STDMETHODIMP CCComTest::Three(BSTR * p_ret)
{
_bstr_t bstr = _bstr_t( _variant_t( len ) );//传递一个字符串类型
* p_ret = bstr.copy( true );
return S_OK;
}
这样整个测试的COM就算完成了,点编译即可编译出一个DLL,IDE会自动将这个DLL注册
C++调用方法(for VC)
还是在刚才的工程里面,增加一个win32 的console工程,使用默认的工程选项.
在stdafx.h中添加如下代码
#include < atlbase.h >
#include < atlcom.h >
一下是UseCom.cpp的代码,注意前面的#include"stdafx.h"别删掉
static _ATL_FUNC_INFO FunInfo = {CC_STDCALL, VT_EMPTY, 2 ,VT_BSTR,VT_VARIANT};
class Event: public IDispEventSimpleImpl < 1 , Event, & __IComTestEvents >
{
public :
BEGIN_SINK_MAP(Event)
SINK_ENTRY_INFO( 1 ,__IComTestEvents, 0x1 ,OnEventOne, & FunInfo)
END_SINK_MAP()
STDMETHOD(OnEventOne)( BSTR P1, VARIANT P2);
};
int _tmain( int argc, _TCHAR * argv[])
{
::CoInitialize(NULL);
{
ICComTestPtr ptr;
ptr.CreateInstance( __uuidof(CComTest) );
Event ev;
ev.DispEventAdvise( ptr );
_variant_t len( 256 );
ptr -> OneFunction( len );
VARIANT var;
ptr -> Two( & var );
SAFEARRAY * p = var.parray;
_bstr_t bstr = ptr -> Three();
}
::CoUninitialize();
return 0 ;
}
GUID const __IComTestEvents = { 0xD1D8E806 , 0x27B2 , 0x44E5
,{ 0xBA , 0x8D , 0x6D , 0x5A , 0xD4 , 0xF3 , 0xA3 , 0x06 } };
STDMETHODIMP Event::OnEventOne( BSTR P1, VARIANT P2)
{
wchar_t sz[ 128 ] = { 0 };
wsprintf( sz, _T( " %s,%d " ) , P1 , P2.lVal );
MessageBoxW( 0 ,sz, 0 , 0 );
return S_OK;
}
编译运行就能看到调用的结果如何了,其中的__IComTestEvents数值是在ComTest工程idl文件中对应的数值. 可以在COM工程的IDL文件中找到,我是为了方便,直接将数值写过来的.
Delphi调用方法
新建一个控制台工程,然后在工程菜单(Porject)中选择导入类型库(Import Type Lib)
在列表框中找到ComTest 1.0 Type Lib(version 1.0)选择Create Unit
之后将将一下代码添加到工程里面,注意uses的时候别改变ComCoreLib_TLB 的路径,这是我机器上对应的路径.
{$APPTYPE CONSOLE}
uses
SysUtils,
ComCoreLib_TLB in ' ....ImportsComCoreLib_TLB.pas ' ,ActiveX,Variants;
type
EventClass = class (TObject)
public
procedure EventOne(AS ender: TObject; const P1: WideString; P2: OleVariant);
end;
var
t:ICComTest;
p_ array:OleVariant;
p_ var:OleVariant;
i:integer;
c: array[ 0 .. 254 ] of byte ;
ev:TCComTest;
ec:EventClass;
{ EventClass }
procedure EventClass.EventOne(AS ender: TObject; const P1: WideString; P2: OleVariant);
begin
writeln( ' event ' , P1 , ' ' , P2 );
end;
begin
CoInitialize(nil);
t : = CoCComTest.Create;
{设置回调}
ev : = TCComTest.Create( nil );
ev.ConnectTo( t );
ec : = EventClass.Create;
ev.OnEventOne : = ec.EventOne;
p_ array : = 255 ;
VarAsType( p_ array , varLongWord );
t.OneFunction( p_ array );
t.Two( p_ var );
writeln( VarArrayDimCount( p_ var ) );
for i: = VarArrayLowBound( p_ var , 1 ) to VarArrayHighBound( p_ var , 1 ) do
begin
c[i] : = p_ var[i];
end;
writeln( t.Three );
readln;
CoUninitialize;
end.
其中代码中CoCComTest.Create;TCComTest.Create( nil );ICComTest;TCComTest;都是在生成的ComCoreLib_TLB 中寻找到的.
VB6调用方法
VB6的方法是最简单的了,project->reference,然后选择ComTest 1.0 Type Lib,然后就能在工程里面使用了.先新建一个模块
ComEventClass.模块代码如下
Option Explicit
Dim Number As Long
Dim buf As Variant
Public Sub test()
Number = 255
Set obj = CreateObject ( " ComCore.CComTest.1 " )
obj.OneFunction (Number)
Call obj.Two(buf) ' obj.Two buf 也可以 但obj.Two(buf)就不行
MsgBox " value " & buf( 5 )
MsgBox (obj.Three())
End Sub
Private Sub obj_EventOne( ByVal P1 As String , ByVal P2 As Variant)
MsgBox P1 & P2
End Sub
调用方法如下
Public Sub Main()
Set a = New ComEventClass
a.test
End Sub
C的调用方法
C的方法是最复杂的方法了,C++还好利用ATL的类能少写很多的代码.但C只能自己来实现,
尤 其是连接点部分,需要自己完成这一些接口.不过这样的好处是对COM的让我连接点部分更加熟悉.首先需要将编写COM的时候所生成的两个文件拷贝过来 ComCore.h和ComCore_i.c这里边包含了C调用的时候所需要的接口.将接口文件添加到工程里面,一下是工程的代码(扩展名为C),如果是 VC的话,注意修改编译选项将编译器设置为C的,
#include < tchar.h >
#include < windows.h >
#define COBJMACROS // 这个宏也是要定义的,并且一定在ComCore.h之前,
// 这样就能够使用ICComTest_OneFunction这样的宏来调用的.
#include " ComCore.h "
void call();
int main( int argc, char * argv[])
{
CoInitialize(NULL);
call();
CoUninitialize();
return 0 ;
}
HRESULT STDMETHODCALLTYPE QueryInterface(_ICComTestEvents * This,REFIID riid, void ** ppvObject)
{
* ppvObject = ( void * )This; // 这是重要的一点,COM会通过这个函数得到_ICComTestEvents的函数指针表.
// 之前是IUnkonwn,因为这是连续的一个空间,所以直接返回首地址即可.
return S_OK;
}
ULONG STDMETHODCALLTYPE AddRef(_ICComTestEvents * This)
{
return 1 ; // 栈上变量,返回任意值都可以.自己保证生存周期
}
ULONG STDMETHODCALLTYPE Release(_ICComTestEvents * This)
{
return 1 ; // 栈上变量,返回任意值都可以.自己保证生存周期
}
HRESULT STDMETHODCALLTYPE Invoke (_ICComTestEvents * This,DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,
/* [out][in] */ DISPPARAMS * pDispParams,
/* [out] */ VARIANT * pVarResult,
/* [out] */ EXCEPINFO * pExcepInfo,
/* [out] */ UINT * puArgErr)
{
if ( 1 == dispIdMember ){ // dispIdMember函数调用序号,这个可以在idl文件中看到
wchar_t sz[ 128 ] = { 0 };
BSTR p1 = pDispParams -> rgvarg[ 1 ].bstrVal;
long p2 = pDispParams -> rgvarg[ 0 ].lVal;
wsprintf( sz, _T( " %s,%d " ) , p1 , p2 );
MessageBoxW( 0 ,sz, 0 , 0 );
}
return S_OK;
}
void call()
{
VARIANT len = {VT_EMPTY};
VARIANT var = {VT_EMPTY};
ICComTest * pComTest = 0 ;
IConnectionPointContainer * pCPC = 0 ;
IConnectionPoint * pCP = 0 ;
_ICComTestEvents * pEvent = 0 ;
SAFEARRAY * pArray = 0 ;
BSTR bstr = 0 ;
DWORD dwCookie = 0 ;
_ICComTestEventsVtbl _pFun = {QueryInterface,AddRef,Release, 0 , 0 , 0 ,Invoke}; // 初始化连接点对象的成员函数
// 其中前三个和最后一个Invoke是必须的,COM对象会调用前三个进行接口查询,调用最后一个进行时间通知
_ICComTestEvents _event = { & _pFun }; // 初始化连接点对象
CoCreateInstance( & CLSID_CComTest , NULL , CLSCTX_ALL , & IID_ICComTest , ( void ** ) & pComTest ); // 得到ICComTest接口
ICComTest_QueryInterface( pComTest , & IID_IConnectionPointContainer , ( void ** ) & pCPC ); // 得到连接点容器
IConnectionPointContainer_FindConnectionPoint( pCPC , & DIID__ICComTestEvents , & pCP ); // 得到连接点
IConnectionPoint_Advise(pCP , (IUnknown * ) & _event , & dwCookie ); // 挂接连接点
len.vt = VT_I4;
len.lVal = 250 ;
ICComTest_OneFunction( pComTest , len ); // 函数调用,激发连接点事件
ICComTest_Two( pComTest , & var );
pArray = var.parray;
ICComTest_Three( pComTest , & bstr);
SysFreeString( bstr );
IConnectionPoint_Unadvise( pCP , dwCookie ); // 断开连接点
IConnectionPointContainer_Release( pCPC ); // 释放资源
IConnectionPoint_Release( pCP );
ICComTest_Release( pComTest );
}
附注
COM工程中的IDL文件
//
// This file will be processed by the MIDL tool to
// produce the type library (ComCore.tlb) and marshalling code.
import " oaidl.idl " ;
import " ocidl.idl " ;
[
object ,
uuid(EC7A5E88 - BD84 - 4C7A - 989B - F5724E3BD3B8),
dual,
nonextensible,
helpstring( " ICComTest Interface " ),
pointer_default(unique)
]
interface ICComTest : IDispatch{
[id( 1 ), helpstring( " method OneFunction " )] HRESULT OneFunction(VARIANT p_array);
[id( 2 ), helpstring( " method Two " )] HRESULT Two([ out ] VARIANT * p_var);
[id( 3 ), helpstring( " method Three " )] HRESULT Three([ out ,retval] BSTR * p_ret);
};
[
uuid(66DE6EF7 - 034D - 4691 - A65B - C8CE496D0644),
version( 1.0 ),
helpstring( " ComCore 1.0 Type Library " )
]
library ComCoreLib
{
importlib( " stdole2.tlb " );
[
uuid(D1D8E806 - 27B2 - 44E5 - BA8D - 6D5AD4F3A306),
helpstring( " _ICComTestEvents Interface " )
]
dispinterface _ICComTestEvents
{
properties:
methods:
[id( 1 ), helpstring( " method EventOne " )] HRESULT EventOne(BSTR p1, VARIANT p2);
};
[
uuid(6E99DDF6 - 9BE7 - 4CC4 - A754 - E4618C730901),
helpstring( " CComTest Class " )
]
coclass CComTest
{
[ default ] interface ICComTest;
[ default , source] dispinterface _ICComTestEvents;
};
};