上一篇說道了使用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文件
// 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;
};
};