上一篇说道了使用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文件就会看到三个函数的空实现(我删掉了注释)
STDMETHODIMP CCComTest::OneFunction(VARIANT p_array)
{
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
<
atlsafe.h
>
#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中添加如下代码
#import
"
..ComCoreDebugComCore.dll
"
no_namespace
//
这里的路径COM的dll的路径.
#include
<
atlbase.h
>
#include
<
atlcom.h
>
一下是UseCom.cpp的代码,注意前面的#include"stdafx.h"别删掉
extern
GUID
const
__IComTestEvents;
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 的路径,这是我机器上对应的路径.
program Project2;
{$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.模块代码如下
Dim
WithEvents
obj
As
ComCoreLib.CComTest
'
WithEvents只能在class模块中使用
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
调用方法如下
Dim
a
As
ComEventClass
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<stdio.h>
#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文件
// ComCore.idl : IDL source for ComCore
//
// 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;
};
};