Understanding Classic COM Interoperability With .NET Applications

最近在研究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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值