前言:为了介绍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的方法