最近在研究C++ 与C# 的interoperability, 所以收藏了一些资料:
前言:为了介绍C#写界面,C++写算法的快捷交互开发方式,首先介绍c++,C#内部的DLL,COM调用。 一,COM COM (Component Object Model),微软为提高代码的可从用性而开发的组件对象模型的软件架构,在windows系统的开发中大量的使用了这种技术,使用这种技术我们尽可能的把我们的软件划分位许多组件,通过组件的组合调用最总实现软件的目的,COM的使用不仅大大的提高了代码的可从用性,而且减小了代码间的耦合。更多的关于OLE,COM,COM+,DCOM,ActiveX的概念 。 二,COM的创建 一般COM的创建有2中方法,使用ATL Wizard 和不使用ATL。 1)使用ATL Wizard创建COM非常的简单,可以参考下面2个链接,分别使用ATL6.0和ATL7.0,最新的类似: ATL7.0 COM: http://www.codeproject.com/atl/SimpleDlls.asp ATL6.0 com: http://www.codeproject.com/atl/com_atl.asp 2)不使用ATL,当然更没有Wizard,只有手动的一步一步的实现,不过这也是学习COM最好的方法和必经之路。可以参考: http://www.codeproject.com/com/LocalCOMServerClient.asp 具体的步骤如下: 1)建立一个Win32的DLL。例如CarLocalServer。 2)首先加入IDL接口描述文件CarLocalServerTypeInfo.idl,编译后会生成4个文件CarLocalServerTypeInfo_h.h,CarLocalServerTypeInfo_i.cpp,CarLocalServerTypeInfo_p.cpp, dlldata.cpp 。
import
"
oaidl.idl
"
; import
"
ocidl.idl
"
;
//
define IStats interface [
object
, uuid(FE78387F
-
D150
-
4089
-
832C
-
BBF02402C872), oleautomation, helpstring(
"
Get the status information about this car
"
)] interface IStats : IUnknown { HRESULT DisplayStats(); HRESULT GetPetName([out,retval] BSTR
*
petName); };
//
define the IEngine interface [
object
, uuid(E27972D8
-
717F
-
4516
-
A82D
-
B688DC70170C), oleautomation, helpstring(
"
Rev your car and slow it down
"
)] interface IEngine : IUnknown { HRESULT SpeedUp(); HRESULT GetMaxSpeed([out,retval]
int
*
maxSpeed); HRESULT GetCurSpeed([out,retval]
int
*
curSpeed); };
//
define the ICreateMyCar interface [
object
, uuid(5DD52389
-
B1A4
-
4fe7
-
B131
-
0F8EF73DD175), oleautomation, helpstring(
"
This lets you create a car object
"
)] interface ICreateMyCar : IUnknown { HRESULT SetPetName([in]BSTR petName); HRESULT SetMaxSpeed([in]
int
maxSp); };
//
library statement [uuid(957BF83F
-
EE5A
-
42eb
-
8CE5
-
6267011F0EF9), version(
1.0
), helpstring(
"
Car server with typeLib
"
)] library CarLocalServerLib { importlib(
"
stdole32.tlb
"
); [uuid(1D66CBA8
-
CCE2
-
4439
-
8596
-
82B47AA44E43)] coclass MyCar { [default] interface ICreateMyCar; interface IStats; interface IEngine; }; };
3) 加入生命周期管理类managesycle.h ,
#pragma once class CManageSycle {
public
: CManageSycle(void); ~CManageSycle(void); static void InObject() {
++
m_nObject;} static void DeObject() {
--
m_nObject;} static bool IsZeroObject() { return m_nObject
==
0
;} static void InLock() {
++
m_nLock;} static void DeLock() {
--
m_nLock;} static bool IsZeroLock() { return m_nLock
==
0
; }
private
: static ULONG m_nObject; static ULONG m_nLock; };
managesycle.cpp 实现文件:
#include
"
StdAfx.h
"
#include
"
managesycle.h
"
ULONG CManageSycle::m_nObject
=
0
; ULONG CManageSycle::m_nLock
=
0
; CManageSycle::CManageSycle(void) { } CManageSycle::~CManageSycle(void) { }
4)加入真正的com的接口的实现类,MyCar.h (除了实现COM的接口,还必须实现IUnKnown接口)
#pragma once #include
"
unknwn.h
"
#include
"
CarLocalServerTypeInfo_h.h
"
const
int
MAX_SPEED
=
500
;
const
int
MAX_NAME_LENGTH
=
20
; class MyCar :
public
IEngine,
public
ICreateMyCar,
public
IStats {
public
: MyCar(); virtual ~MyCar();
//
IUnknown STDMETHODIMP QueryInterface(REFIID riid, void
**
pIFace); STDMETHODIMP_(DWORD)AddRef(); STDMETHODIMP_(DWORD)Release();
//
IEngine STDMETHODIMP SpeedUp(); STDMETHODIMP GetMaxSpeed(
int
*
maxSpeed); STDMETHODIMP GetCurSpeed(
int
*
curSpeed);
//
IStats STDMETHODIMP DisplayStats(); STDMETHODIMP GetPetName(BSTR
*
petName);
//
ICreateMyCar STDMETHODIMP SetPetName(BSTR petName); STDMETHODIMP SetMaxSpeed(
int
maxSp);
private
: DWORD m_refCount; BSTR m_petName;
int
m_maxSpeed;
int
m_currSpeed; };
MyCar.cpp
#include
"
stdafx.h
"
#include
<
stdio.h
>
#include
"
CarLocalServerTypeInfo_i.c
"
#include
"
MyCar.h
"
#include
"
ManageSycle.h
"
//
//
Construction
/
Destruction
//
MyCar::MyCar() : m_refCount(
0
), m_currSpeed(
0
), m_maxSpeed(
0
) { m_refCount
=
0
; CManageSycle::InObject(); m_petName
=
SysAllocString(L
"
Default Pet Name
"
); } MyCar::~MyCar() { CManageSycle::DeObject();
if
(m_petName) SysFreeString(m_petName); MessageBox(
NULL
, L
"
MyCar is being distructed. Make sure you see this message, if not, you might have memory leak!
"
, L
"
Destructor
"
,MB_OK | MB_SETFOREGROUND); }
//
IUnknown STDMETHODIMP MyCar::QueryInterface(REFIID riid, void
**
pIFace) {
//
Which aspect of me
do
they want?
if
(riid
==
IID_IUnknown) {
*
pIFace
=
(IUnknown
*
)(IEngine
*
)this;
//
MessageBox(
NULL
,
"
Handed out IUnknown
"
,
"
QI
"
,MB_OK | MB_SETFOREGROUND); }
else
if
(riid
==
IID_IEngine) {
*
pIFace
=
(IEngine
*
)this;
//
MessageBox(
NULL
,
"
Handed out IEngine
"
,
"
QI
"
,MB_OK | MB_SETFOREGROUND); }
else
if
(riid
==
IID_IStats) {
*
pIFace
=
(IStats
*
)this;
//
MessageBox(
NULL
,
"
Handed out IStats
"
,
"
QI
"
,MB_OK | MB_SETFOREGROUND); }
else
if
(riid
==
IID_ICreateMyCar) {
*
pIFace
=
(ICreateMyCar
*
)this;
//
MessageBox(
NULL
,
"
Handed out ICreateMyCar
"
,
"
QI
"
,MB_OK | MB_SETFOREGROUND); }
else
{
*
pIFace
=
NULL
; return E_NOINTERFACE; } ((IUnknown
*
)(
*
pIFace))
->
AddRef(); return S_OK; } STDMETHODIMP_(DWORD) MyCar::AddRef() {
++
m_refCount; return m_refCount; } STDMETHODIMP_(DWORD) MyCar::Release() {
if
(
--
m_refCount
==
0
) { delete this; return
0
; }
else
return m_refCount; }
//
IEngine STDMETHODIMP MyCar::SpeedUp() { m_currSpeed
+=
10
; return S_OK; } STDMETHODIMP MyCar::GetMaxSpeed(
int
*
maxSpeed) {
*
maxSpeed
=
m_maxSpeed; return S_OK; } STDMETHODIMP MyCar::GetCurSpeed(
int
*
curSpeed) {
*
curSpeed
=
m_currSpeed; return S_OK; }
//
IStats STDMETHODIMP MyCar::DisplayStats() {
//
Need
to
transfer a BSTR
to
a char
array
. char buff[MAX_NAME_LENGTH]; WideCharToMultiByte(CP_ACP,
NULL
, m_petName,
-
1
, buff, MAX_NAME_LENGTH,
NULL
,
NULL
);
//
MessageBox(
NULL
, buff, L
"
Pet Name
"
,MB_OK | MB_SETFOREGROUND); memset(buff,
0
, sizeof(buff)); sprintf(buff,
"
%d
"
, m_maxSpeed);
//
MessageBox(
NULL
, buff,L
"
Max Speed
"
, MB_OK| MB_SETFOREGROUND); return S_OK; } STDMETHODIMP MyCar::GetPetName(BSTR
*
petName) {
*
petName
=
SysAllocString(m_petName); return S_OK; }
//
ICreateMyCar STDMETHODIMP MyCar::SetPetName(BSTR petName) { SysReAllocString(
&
m_petName, petName); return S_OK; } STDMETHODIMP MyCar::SetMaxSpeed(
int
maxSp) {
if
(maxSp
<
MAX_SPEED) m_maxSpeed
=
maxSp; return S_OK; }
5)加入工厂类MyCarClassFactory.h,(实现IUnKnown接口和IClassFactory接口)
//
the class
object
(class factory)
for
CoMyCar class #pragma once class MyCarClassFactory :
public
IClassFactory {
public
: MyCarClassFactory(); virtual ~MyCarClassFactory();
//
IUnknown STDMETHODIMP QueryInterface(REFIID riid,void
**
pIFace); STDMETHODIMP_(ULONG)AddRef(); STDMETHODIMP_(ULONG)Release();
//
IClassFactory STDMETHODIMP LockServer(BOOL fLock); STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter,REFIID riid,void
**
ppv);
private
: ULONG m_refCount; };
MyCarClassFactory.cpp
#include
"
stdafx.h
"
#include
"
MyCar.h
"
#include
"
MyCarClassFactory.h
"
#include
"
locks.h
"
#include
"
ManageSycle.h
"
MyCarClassFactory::MyCarClassFactory() { m_refCount
=
0
; } MyCarClassFactory::~MyCarClassFactory() { MessageBox(
NULL
, L
"
MyCarClassFactory is being distructed. Make sure you see this message, if not, you might have memory leak!
"
, L
"
Destructor
"
,MB_OK | MB_SETFOREGROUND); } STDMETHODIMP_(ULONG) MyCarClassFactory::AddRef() {
//
return
++
m_refCount; return
10
; } STDMETHODIMP_(ULONG) MyCarClassFactory::Release() {
/*
if
(
--
m_refCount
==
0
) { delete this; return
0
; } return m_refCount;
*/
return
20
; } STDMETHODIMP MyCarClassFactory::QueryInterface(REFIID riid,void
**
pIFace) {
if
( riid
==
IID_IUnknown )
*
pIFace
=
(IUnknown
*
)this;
else
if
( riid
==
IID_IClassFactory )
*
pIFace
=
(IClassFactory
*
)this;
else
{
*
pIFace
=
NULL
; return E_NOINTERFACE; } ((IUnknown
*
)(
*
pIFace))
->
AddRef(); return S_OK; } STDMETHODIMP MyCarClassFactory::LockServer(BOOL fLock) { (VARIANT_TRUE
==
fLock) ? CManageSycle::InLock() : CManageSycle::DeLock(); return S_OK; } STDMETHODIMP MyCarClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,REFIID riid,void
**
ppv) {
if
( pUnkOuter !
=
NULL
) return CLASS_E_NOAGGREGATION; MyCar
*
pMyCarObj
=
NULL
; HRESULT hr; pMyCarObj
=
new
MyCar(); hr
=
pMyCarObj
->
QueryInterface(riid,ppv);
if
( FAILED(hr) ) delete pMyCarObj; return hr; }
6)实现COM入口和自注册函数CarLocalServer.cpp,
//
CarLocalServer.cpp : Defines the entry point
for
the DLL application.
//
#include
"
stdafx.h
"
#include
<
iostream
>
#include
<
string
.h
>
#include
"
CarLocalServerTypeInfo_h.h
"
//
#include
"
CarLocalServerTypeInfo_i.c
"
#include
"
MyCarClassFactory.h
"
#include
"
ManageSycle.h
"
using namespace std; #ifdef _MANAGED #pragma managed(push, off) #endif HMODULE g_hmodule; BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { g_hmodule
=
static_cast
<
HMODULE
>
(hModule); return
TRUE
; } STDAPI DllCanUnloadNow() { bool bDllCanUnloadNow
=
CManageSycle::IsZeroObject()
&&
CManageSycle::IsZeroLock(); return bDllCanUnloadNow ? S_OK : S_FALSE; } STDAPI DllGetClassObject(REFCLSID rclsid,REFIID riid,LPVOID
*
ppv) {
*
ppv
=
NULL
;
if
(__uuidof(MyCar) !
=
rclsid) { return E_NOINTERFACE; } MyCarClassFactory
*
pBirdFactory
=
new
MyCarClassFactory();
if
(
NULL
==
pBirdFactory) { return E_OUTOFMEMORY; } HRESULT hr
=
pBirdFactory
->
QueryInterface(riid,ppv);
if
(FAILED(hr)) { delete pBirdFactory; } return hr; } STDAPI DllRegisterServer() { HKEY hRoot, hNew; ::RegOpenKey(HKEY_CLASSES_ROOT,L
"
CLSID
"
,
&
hRoot); ::RegCreateKey(hRoot,L
"
{1F0A9759-FCBE-4870-8336-971BD19A7452}//InprocServer32
"
,
&
hNew); wchar_t strFile[MAX_PATH]; ::GetModuleFileName(g_hmodule,strFile,MAX_PATH); ::RegSetValue(hNew,
NULL
,REG_SZ,strFile,MAX_PATH); ::RegCloseKey(hRoot); return S_OK; } STDAPI DllUnregisterServer() { HKEY hRoot; ::RegOpenKey(HKEY_CLASSES_ROOT,L
"
CLSID
"
,
&
hRoot); ::RegDeleteKey(hRoot,L
"
{1F0A9759-FCBE-4870-8336-971BD19A7452}
"
); ::RegDeleteKey(hRoot,L
"
{1F0A9759-FCBE-4870-8336-971BD19A7452}//InprocServer32
"
); ::RegCloseKey(hRoot); return S_OK; } #ifdef _MANAGED #pragma managed(pop) #endif
7)增加def到处文件CarLocalServer.def,
LIBRARY
"
CarLocalServer
"
EXPORTS DllCanUnloadNow
PRIVATE
DllGetClassObject
PRIVATE
DllRegisterServer
PRIVATE
DllUnregisterServer
PRIVATE
三,COM的调用过程 通过一个创建COM组件的最小框架结构,然后看一看其内部处理流程是怎样的
IUnknown *pUnk=NULL; IObject *pObject=NULL; CoInitialize(NULL); CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void**)&pUnk); pUnk->QueryInterface(IID_IOjbect, (void**)&pObject); pUnk->Release(); pObject->Func(); pObject->Release(); CoUninitialize();
这就是一个典型的创建COM组件的框架,不过我的兴趣在CoCreateInstance身上,让我们来看看它内部做了一些什么事情。 以下是它内部实现的一个伪代码:
CoCreateInstance(....) { ....... IClassFactory *pClassFactory=NULL; CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClassFactory); pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk); pClassFactory->Release(); ........ }
这段话的意思就是先得到类厂对象,再通过类厂创建组件从而得到IUnknown指针。继续深入一步,看看CoGetClassObject的内部伪码:
CoGetClassObject(.....) { //通过查注册表CLSID_Object,得知组件DLL的位置、文件名 //装入DLL库 //使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针。 //调用DllGetClassObject } DllGetClassObject是干什么的,它是用来获得类厂对象的。只有先得到类厂才能去创建组件. 下面是DllGetClassObject的伪码: DllGetClassObject(...) { ...... CFactory* pFactory= new CFactory; //类厂对象 pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory); //查询IClassFactory指针 pFactory->Release(); ...... } CoGetClassObject的流程已经到此为止,现在返回CoCreateInstance,看看CreateInstance的伪码: CFactory::CreateInstance(.....) { ........... CObject *pObject = new CObject; //组件对象 pObject->QueryInterface(IID_IUnknown, (void**)&pUnk); pObject->Release(); ........... }
四,COM的调用方法
对上面手动创建的COM的调用实例:
int
main() {
//
initialize the COM runtime cout
<<
"
Initialize the COM runtime
"
; CoInitialize(
NULL
); cout
<<
"
success.
"
<<
endl;
//
declare variables HRESULT hr; IClassFactory
*
pICF
=
NULL
; ICreateMyCar
*
pICreateMyCar
=
NULL
; IEngine
*
pIEngine
=
NULL
; IStats
*
pIStats
=
NULL
; cout
<<
endl
<<
"
Get the class factory interface for the Car class
"
; hr
=
CoGetClassObject(CLSID_MyCar,CLSCTX_LOCAL_SERVER,
NULL
,IID_IClassFactory,(void
**
)
&
pICF);
if
( FAILED(hr) ) { cout
<<
"
fail
"
;
exit
(
1
); }
else
cout
<<
"
success.
"
<<
endl; cout
<<
"
Create the Car object and get back the ICreateMyCar interface
"
; hr
=
pICF
->
CreateInstance(
NULL
,IID_ICreateMyCar,(void
**
)
&
pICreateMyCar);
if
( FAILED(hr) ) {
//
ShowErrorMessage(
"
CoGetClassObject()
"
,hr);
exit
(
1
); }
else
cout
<<
"
success.
"
<<
endl;
//
set
parameters
on
the car cout
<<
endl
<<
"
Set different parameters on the car
"
; pICreateMyCar
->
SetMaxSpeed(
30
); BSTR carName
=
SysAllocString(OLESTR(
"
COMCar?!
"
)); pICreateMyCar
->
SetPetName(carName); SysFreeString(carName); cout
<<
"
success.
"
<<
endl; cout
<<
endl
<<
"
Query the IStats interface
"
; pICreateMyCar
->
QueryInterface(IID_IStats,(void
**
)
&
pIStats); cout
<<
"
success.
"
<<
endl; cout
<<
endl
<<
"
Use the IStats interface to display the status of the car:
"
<<
endl; pIStats
->
DisplayStats(); cout
<<
endl
<<
"
Query the IEngine interface
"
; pICreateMyCar
->
QueryInterface(IID_IEngine,(void
**
)
&
pIEngine); cout
<<
"
success.
"
<<
endl; cout
<<
endl
<<
"
Start to use the engine
"
<<
endl;
int
curSp
=
0
;
int
maxSp
=
0
; pIEngine
->
GetMaxSpeed(
&
maxSp);
do
{ pIEngine
->
SpeedUp(); pIEngine
->
GetCurSpeed(
&
curSp); cout
<<
"
current speed is:
"
<<
curSp
<<
endl; }
while
(curSp
<=
maxSp); cout
<<
endl
<<
"
Report status again:
"
<<
endl; pIStats
->
DisplayStats();
if
( pICF ) pICF
->
Release();
if
( pICreateMyCar) pICreateMyCar
->
Release();
if
( pIStats ) pIStats
->
Release();
if
( pIEngine ) pIEngine
->
Release(); cout
<<
endl
<<
"
Close the COM runtime
"
; CoUninitialize(); cout
<<
"
success.
"
<<
endl; return
0
; }
方法一:向上面的调用,通过包含#include "../CarLocalServer/CarLocalServerTypeInfo_h.h"和#include "../CarLocalServer/CarLocalServerTypeInfo_i.c" 方法二:使用#import导入tlb 可以使用: CComBSTR ,CComPtr<> 和 CComQIPtr<> 等来简化调用。 五,总结 COM比一般的DLL有很多的优点,COM 没有重名问题,因为根本不是通过函数名来调用函数,而是通过虚函数表,自然也不会有函数名修饰的问题。路径问题也不复存在,因为是通过查注册表来找组件的,放在什么地方都可以,即使在别的机器上也可以。也不用考虑和 EXE 的依赖关系了,它们二者之间是松散的结合在一起,可以轻松的换上组件的一个新版本,而应用程序混然不觉。 但是COM仍然是有问题的,比如说版本控制的问题,.NET将逐步代替COM的使用。
codeproject上面有一篇比较好的文章:
http://www.codeproject.com/dotnet/cominterop.asp