::  ::  ::  ::  :: 管理

使用COM方式实现不同语言之间的调用

Posted on 2008-06-16 04:59 nt05 阅读(315) 评论(0)  编辑 收藏 引用 所属分类: cpp

上一篇说道了使用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_ arrayvarLongWord );
  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)
{
    
if1 == 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, 00);
    }
    
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
    {
        [
defaultinterface ICComTest;
        [
default, source] dispinterface _ICComTestEvents;
    };
};