前言:為了介紹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的使用。
六,參考
1)OLE/COM/COM+/DCOM/ActiveX/ActiveX contorl ( 概念)
2)用VC進行COM編程所必須掌握的理論知識
3)COM編程入門(1)
4)COM編程入門(2)
5)c++中使用com的方法