前言:為了介紹C#寫界面,C++寫算法的快捷交互開(kāi)發(fā)方式,首先介紹c++,C#內(nèi)部的DLL,COM調(diào)用。
一,COM
COM (Component Object Model),微軟為提高代碼的可從用性而開(kāi)發(fā)的組件對(duì)象模型的軟件架構(gòu),在windows系統(tǒng)的開(kāi)發(fā)中大量的使用了這種技術(shù),使用這種技術(shù)我們盡可能的把我們的軟件劃分位許多組件,通過(guò)組件的組合調(diào)用最總實(shí)現(xiàn)軟件的目的,COM的使用不僅大大的提高了代碼的可從用性,而且減小了代碼間的耦合。更多的關(guān)于OLE,COM,COM+,DCOM,ActiveX的概念。
二,COM的創(chuàng)建
一般COM的創(chuàng)建有2中方法,使用ATL Wizard 和不使用ATL。
1)使用ATL Wizard創(chuàng)建COM非常的簡(jiǎn)單,可以參考下面2個(gè)鏈接,分別使用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,當(dāng)然更沒(méi)有Wizard,只有手動(dòng)的一步一步的實(shí)現(xiàn),不過(guò)這也是學(xué)習(xí)COM最好的方法和必經(jīng)之路。可以參考:
http://www.codeproject.com/com/LocalCOMServerClient.asp
具體的步驟如下:
1)建立一個(gè)Win32的DLL。例如CarLocalServer。
2)首先加入IDL接口描述文件CarLocalServerTypeInfo.idl,編譯后會(huì)生成4個(gè)文件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實(shí)現(xiàn)文件:
#include "StdAfx.h"
#include "managesycle.h"
ULONG CManageSycle::m_nObject=0;
ULONG CManageSycle::m_nLock=0;
CManageSycle::CManageSycle(void)
{
}

CManageSycle::~CManageSycle(void)
{
}
4)加入真正的com的接口的實(shí)現(xiàn)類,MyCar.h (除了實(shí)現(xiàn)COM的接口,還必須實(shí)現(xiàn)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,(實(shí)現(xiàn)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)實(shí)現(xiàn)COM入口和自注冊(cè)函數(shù)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的調(diào)用過(guò)程
通過(guò)一個(gè)創(chuàng)建COM組件的最小框架結(jié)構(gòu),然后看一看其內(nèi)部處理流程是怎樣的
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();
|
這就是一個(gè)典型的創(chuàng)建COM組件的框架,不過(guò)我的興趣在CoCreateInstance身上,讓我們來(lái)看看它內(nèi)部做了一些什么事情。
以下是它內(nèi)部實(shí)現(xiàn)的一個(gè)偽代碼:
CoCreateInstance(....) { ....... IClassFactory *pClassFactory=NULL; CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClassFactory); pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk); pClassFactory->Release(); ........ }
|
這段話的意思就是先得到類廠對(duì)象,再通過(guò)類廠創(chuàng)建組件從而得到IUnknown指針。繼續(xù)深入一步,看看CoGetClassObject的內(nèi)部偽碼:
CoGetClassObject(.....) { //通過(guò)查注冊(cè)表CLSID_Object,得知組件DLL的位置、文件名 //裝入DLL庫(kù) //使用函數(shù)GetProcAddress(...)得到DLL庫(kù)中函數(shù)DllGetClassObject的函數(shù)指針。 //調(diào)用DllGetClassObject } DllGetClassObject是干什么的,它是用來(lái)獲得類廠對(duì)象的。只有先得到類廠才能去創(chuàng)建組件. 下面是DllGetClassObject的偽碼: DllGetClassObject(...) { ...... CFactory* pFactory= new CFactory; //類廠對(duì)象 pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory); //查詢IClassFactory指針 pFactory->Release(); ...... } CoGetClassObject的流程已經(jīng)到此為止,現(xiàn)在返回CoCreateInstance,看看CreateInstance的偽碼: CFactory::CreateInstance(.....) { ........... CObject *pObject = new CObject; //組件對(duì)象 pObject->QueryInterface(IID_IUnknown, (void**)&pUnk); pObject->Release(); ........... }
|
四,COM的調(diào)用方法
對(duì)上面手動(dòng)創(chuàng)建的COM的調(diào)用實(shí)例:
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;
}
方法一:向上面的調(diào)用,通過(guò)包含#include "../CarLocalServer/CarLocalServerTypeInfo_h.h"和#include "../CarLocalServer/CarLocalServerTypeInfo_i.c"
方法二:使用#import導(dǎo)入tlb
可以使用: CComBSTR ,CComPtr<> 和 CComQIPtr<> 等來(lái)簡(jiǎn)化調(diào)用。
五,總結(jié)
COM比一般的DLL有很多的優(yōu)點(diǎn),COM沒(méi)有重名問(wèn)題,因?yàn)楦静皇峭ㄟ^(guò)函數(shù)名來(lái)調(diào)用函數(shù),而是通過(guò)虛函數(shù)表,自然也不會(huì)有函數(shù)名修飾的問(wèn)題。路徑問(wèn)題也不復(fù)存在,因?yàn)槭峭ㄟ^(guò)查注冊(cè)表來(lái)找組件的,放在什么地方都可以,即使在別的機(jī)器上也可以。也不用考慮和EXE的依賴關(guān)系了,它們二者之間是松散的結(jié)合在一起,可以輕松的換上組件的一個(gè)新版本,而應(yīng)用程序混然不覺(jué)。
但是COM仍然是有問(wèn)題的,比如說(shuō)版本控制的問(wèn)題,.NET將逐步代替COM的使用。
六,參考
1)OLE/COM/COM+/DCOM/ActiveX/ActiveX contorl ( 概念)
2)用VC進(jìn)行COM編程所必須掌握的理論知識(shí)
3)COM編程入門(1)
4)COM編程入門(2)
5)c++中使用com的方法