不同語言之間的調用 - ATL COM方式實現

 

上一篇說道了使用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;
    };
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章