您的位置:首页 > 其它

VC调用COM的方法总结

2012-08-06 11:37 471 查看
转载请标明是引用于 http://blog.csdn.net/chenyujing1234



源码下载地址(采用VS2005+Win32):

http://www.rayfile.com/zh-cn/files/fc8fa0f8-df8f-11e1-8c83-0015c55db73d/



在文章<<采用ATL模型代替lib dll 的调用>>中我介绍了VC中调用COM的两种方法:

(1)通过ProgID调用;

(2)通过CLSID调用;

现在介绍其它方法。

一、COM端的设计

1、COM接口的设计

1、新建项目时,选择ATL项目->服务(EXE)

2、接下来我们添加COM接口Account, 设计方法原理可参考我的文章<<采用ATL模型代替lib dll 的调用>>



我们决定导出四个接口:

public:
	STDMETHOD(get_CurrentAccount)(/*[out, retval]*/ double *pVal);
	STDMETHOD(put_CurrentAccount)(/*[in]*/ double newVal);
	STDMETHOD(Withdraw)(/*[in]*/double x);
	STDMETHOD(Deposit)(/*[in]*/double x);


类设计如下:

class ATL_NO_VTABLE CAccount : 
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAccount, &CLSID_Account>,
public IDispatchImpl<IAccount, &IID_IAccount, &LIBID_BANKLib>
{
public:
CAccount()
{
CurrentValue = 0;
}

DECLARE_REGISTRY_RESOURCEID(IDR_ACCOUNT)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CAccount)
COM_INTERFACE_ENTRY(IAccount)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// IAccount
public: STDMETHOD(get_CurrentAccount)(/*[out, retval]*/ double *pVal); STDMETHOD(put_CurrentAccount)(/*[in]*/ double newVal); STDMETHOD(Withdraw)(/*[in]*/double x); STDMETHOD(Deposit)(/*[in]*/double x);

protected:
double CurrentValue;
};


在设计完成COM接口后,我们编写注册脚本文件Account.rgs(参考文章<<ATL产生的RGS文件介绍>>):

HKCR
{
	NoRemove AppID
	{
		{2F2CD476-6C09-4CFD-A819-7BE66BBE2CAF} = s 'Bank'
		'Bank.EXE'
		{
			val AppID = s {2F2CD476-6C09-4CFD-A819-7BE66BBE2CAF}
		}
	}
}

并把得到的.rgs文件导入到Res资源文件中。(目的将会在下面看到)



2、COM端提供服务有两种方式(测试时只要任选一种就可以了):

做为一个COM的服务器,为了实现服务器的调用逻辑,我们设计一个服务器的类,它继承自CComModule。

class CExeModule : public CComModule
{
public:
	LONG Unlock();
	DWORD dwThreadID;
	HANDLE hEventShutdown;
	void MonitorShutdown();
	bool StartMonitor();
	bool bActivity;
};

且我们得到服务器模块的对象:

CExeModule _Module;

这里不得不说一下,如果我们在新建项目时选择的是ATL项目,EXE类型,那么就里可能会自动产生:CBankModule _AtlModule;对象,那么当我们添加自已的_Module时

得把自动生成的对象删除。

不管是采用哪种方式我们都要让服务器初始化,目的是将第1、部设计的COM接口和当前的服务器对象绑定:

_Module.Init(ObjectMap, hInstance, &LIBID_BANKLib);

这里的变量ObjectMap,通过以下系统宏的方法定义:

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Account, CAccount)
END_OBJECT_MAP()

宏定义为:

#define BEGIN_OBJECT_MAP(x) static ATL::_ATL_OBJMAP_ENTRY x[] = {
#define END_OBJECT_MAP()   {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};
#define OBJECT_ENTRY(clsid, class) {&clsid, class::UpdateRegistry, class::_ClassFactoryCreatorClass::CreateInstance, class::_CreatorClass::CreateInstance, NULL, 0, class::GetObjectDescription, class::GetCategoryMap, class::ObjectMain },

这三个宏可以构建一个COM类静态链表(数组),链表的元素就是上面的_ATL_OBJMAP_ENTRY结构,它的第二个成员就是注册时被调用的。

分解开来就是:

static ATL::_ATL_OBJMAP_ENTRY ObjectMap[] = 
{
	 {
		&CLSID_Account, 
		CAccount::UpdateRegistry, 
		CAccount::_ClassFactoryCreatorClass::CreateInstance, 
		CAccount::_CreatorClass::CreateInstance, 
		NULL, 
		0, 
		CAccount::GetObjectDescription, 
		CAccount::GetCategoryMap, 
		CAccount::ObjectMain 
	},
	{NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
};




(1)注册COM服务器方式
由于在第1、步中我们把Account.rgs导入到RES文件中,所以注册服务器前得通过这个资源类型找到rgs文件,然后调用RegisterServer注册:

_Module.UpdateRegistryFromResource(IDR_Bank, TRUE);
            nRet = _Module.RegisterServer(TRUE);

扩展知识:UpdateRegistryFromResource
我们来看UpdateRegistryFromResource定义:

#ifdef _ATL_STATIC_REGISTRY
#define UpdateRegistryFromResource UpdateRegistryFromResourceS
#else
#define UpdateRegistryFromResource UpdateRegistryFromResourceD
#endif // _ATL_STATIC_REGISTRY

UpdateRegistryFromResourceS与UpdateRegistryFromResourceD分别为ATL静态链接与动态链接版本,下面看UpdateRegistryFromResourceD动态链接版本:

// Statically linking to Registry Ponent
	virtual HRESULT WINAPI UpdateRegistryFromResourceS(LPCTSTR lpszRes, BOOL bRegister,
		struct _ATL_REGMAP_ENTRY* pMapEntries = NULL) throw()
	{
#ifdef _ATL_STATIC_REGISTRY
		return CAtlModuleT<CComModule>::UpdateRegistryFromResourceS(lpszRes, bRegister, pMapEntries);
#else
		(lpszRes);
		(bRegister);
		(pMapEntries);
		return E_FAIL;
#endif
	}


它调用的是AtlModuleUpdateRegistryFromResourceD,其位于ATLBASE.h中,下面是其原型:

inline HRESULT WINAPI CAtlModule::UpdateRegistryFromResourceS(LPCTSTR lpszRes, BOOL bRegister,
	struct _ATL_REGMAP_ENTRY* pMapEntries /*= NULL*/)

查看它的源代码就会发现,它调用的是IRegistrar::ResourceRegister/ResourceUnregister等进行注册与反注册。IRegistar是什么?它应该是一个用来注册COM的Shell接口,具体可查看MSDN的“The ATL Registry Component (Registrar)”主题。

由于到了这里不能看到它的源代码了,所以不知道它的具体实现,但通过搜索ResourceRegister/ResourceUnregister,意外地发现它们也是另一个类CRegObject的成员函数。



它们最终调用的是CRegObject::RegisterFromResource,其中调用了CRegParser::RegisterBuffer,这个可以看作是注册与反注册的终极靶标了。通过查看这个函数,而且根据前面的函数名及需要传递的资源ID,你会恍然大悟:它实际上是通过解析ATL向导生成的rgs文件实现注册与反注册了,其根本操作就是添加或删除注册表项。这又回到了注册与反注册的最原始的方法了。



(2)运行COM服务EXE方式
在运行COM服务EXE前只需要注册类对象即可:

hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, 
            REGCLS_MULTIPLEUSE);


注册的类对象通过之前Init已经实现了:

_Module.Init(ObjectMap, hInstance, &LIBID_BANKLib);

注意:由于采用的运行EXE方式,只要此EXE进程退出,客户端即会出现调用不到接口的现象,虽然不会报接口出错,但得到的数据都是0。

3、方便调试的循环接口

为了调试COM的方便,我们让main函数进入一个循环,方法如下:

  if (bRun)
{
_Module.StartMonitor();
#if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);
_ASSERTE(SUCCEEDED(hRes));
hRes = CoResumeClassObjects();
#else
hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE);
#endif
_ASSERTE(SUCCEEDED(hRes));

MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);

_Module.RevokeClassObjects();
Sleep(dwPause); //wait for any threads to finish
}


这样我们就能在COM的接口时定断点,方便与客户端联调了。

二、客户端的设计

客户端要能调用到服务端的接口,必须满足两个条件:

(1)服务器开启了服务;

(2)知道服务器端的IID与CLSID;

1、在客户端我们先定义IID与CLSID:
///////////////////////////////////////CLSID & IID 
const IID IID_IAccount = {0xEF327845,0x40A8,0x4AE0,{0x90,0x50,0xEA,0x7F,0xAD,0x02,0x75,0xEA}};

const IID LIBID_BANKLib = {0x98ECF356,0x06EF,0x4F72,{0xA3,0xA0,0x6E,0xCA,0x37,0x25,0x50,0x9E}};

const CLSID CLSID_Account = {0xEB914BA1,0x3C9C,0x4321,{0xBB,0x87,0xD2,0xF8,0xE3,0xEF,0xEB,0x09}};



2、获得服务器的接口IAccount *pAccount;

法一、
通过CoCreateInstance获得IUnknown指针,然后通过IUnknown指针返回IAccount指针:

//创建对象实例,并返回IUnknown 指针
	hr = CoCreateInstance(CLSID_Account, NULL, 
        CLSCTX_LOCAL_SERVER , IID_IUnknown, (void**)&pUnknown);
	if(FAILED(hr))
	{
		MessageBox("创建对象实例失败!");
		return false;
	}

	//通过IUnkonwn指针去查询接口指针,返回IAccount指针
	hr = pUnknown->QueryInterface(IID_IAccount,(void**)&pAccount);
	if(FAILED(hr))
	{
		MessageBox("没有查找的接口指针!");
		return false;
	}

	pUnknown->Release();



法二、

直接从dll中得到DllGetClassObject,接着生成类对象及类实例(这方法可以使组件不用在注册表里注册),这是最原始的方法,但这样做没什么意义,至少失去了COM

对用户的透明性),不推荐使用。

typedef HRESULT (__stdcall * pfnHello)(REFCLSID,REFIID,void**);
	pfnHello fnHello= NULL;
	HINSTANCE hdllInst = LoadLibrary("组件所在目录myCom.dll");
	fnHello=(pfnHello)GetProcAddress(hdllInst, "DllGetClassObject");
	if (fnHello != 0)
	{
		IClassFactory* pcf = NULL;
		HRESULT hr=(fnHello)(CLSID_Account, IID_IClassFactory,(void**)&pcf);
		if (SUCCEEDED(hr) && (pcf != NULL))
		{
			hr = pcf->CreateInstance(NULL, IID_IAccount, (void**)&pAccount);
			if(FAILED(hr))
			{
				MessageBox("没有查找的接口指针!");
				return false;
			}

		}
	} 
	FreeLibrary(hdllInst);



法三、

通过ClassWizard利用类型库生成包装类,不过前提是com组件的接口必须是派生自IDispatch,具体方法:

(1)调出添加类向导(.NET中),选择类型库中MFC类,打开,选择"文件",选择"myCom.dll"或"myCom.tlb";

(2)接下来会出来该myCom中的所有接口,选择你想生成的接口包装类后,向导会自动生成相应的.h文件。

这样你就可以在你的MFC中像使用普通类那样使用组件了.(CreateDispatch("myCom.GetRes") 中的参数就是ProgID通过Clsid在注册表中可以查询的到)

CoInitialize(NULL);
   CGetRes getRest;
   if (getRest.CreateDispatch("myCom.GetRes") != 0)
   {
   getRest.Hello();
   getRest.ReleaseDispatch();
   }
   CoUninitialize();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: