不用ATL框架纯手工实现COM进程外回调
COM全称为(Component Object Model)组件对象模型。COM是由Microcsoft提出的组件标准,它定义了组件程序之间进行交互的标准。COM组件可分为进程内组件和进程外组件。本编文章主要讲述进程外组件的回调实现。
COM进程外组件模型
COM进程外组件是以独立进程的形式向客户提供对象服务的,客户调用组件程序提供的服务,必然要跨进程调用。在COM中这是通过代理存根来实现的。
客户进程调用COM进程外组件的过程主要为以下6步:
-
客户进程调用COM接口函数 -
代理对象通过LPC调用服务程序中的存根DLL -
在服务进程中的存根DLL调用COM组件对象提供的接口 -
COM组件对象接口执行完毕返回结果给存根DLL -
存根DLL将结果通过LPC传递给客户进程中代理DLL -
在客户进程中的代理DLL再将结果返回给客户
具体调用过程如下图:
客户进程调用COM进程外组件的过程虽然复杂但是这些实现都是对开发人员透明的。开发人员不需要关心底层是怎么实现,开发人员只要像调用正常函数那样去使用COM接口函数就行。正如图中虚线部分一样。
代理存根除了完成LPC调用功能外,他还实现将我们的接口参数的序列化和反序列化。序列化即在客户调用时将接口封装成一个数据包,反序列化即在服务端时将数据包拆解还原。
通过注册表管理COM对象
COM规范使用128位的GUID来标识COM对象和接口,客户进程通过GUID值来创建COM对象并与对象交互。因为客户程序和组件程序是独立的,客户程序在创建COM对象时并不知道组件程序具体的位置。这时我们就需要通过系统注册表提供信息来创建组件对象。 系统注册表是一个全操作系统范围内的公用信息仓库,其中包含了COM组件必要的信息。
组件程序需要把它实现的COM对象信息保存到注册表上,这个步骤叫做组件的注册。
-
注册表结构 如下图可以看出注册表包含以下几项:HKEY_CLASSES_ROOT、HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE、HKEY_USERS、HKEY_CURRENT_CONFIG,每个项下面又有若干子项。
![注册表](https://imgkr2.cn-bj.ufileos.com/7f96c0ac-abdb-4710-8221-af352641f048.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=AOurGtIierm7A2PbrxYS%252BVttFt4%253D&Expires=1608520175)
-
COM组件注册信息 COM组件的注册信息在HKEY_CLASSES_ROOT键下,最主要的信息在CLSID键下,该键下列出了当前系统上所有的组件信息。如下图:
在每个CLSID子键下包含了组件对象相关的信息,如进程内组件CLSID子键下包含了InprocServer32子键该键的缺省值为组件的全路径名。进程外组件则包含LocalServer32子键该键的缺省值为进程外组件的全路径名。如下图:
对于进程外组件注册表中还必须有代理DLL和存根DLL的信息, 这些信息被保存在子键名为ProxyStubClsid或者ProxyStubClsid32下。除了使用 CLSID来标识一个COM对象外还可以使用字符串来表示,这个信息被保存在ProgID子键下。ProgID和CLSID可以通过CLSIDFromProgID和ProgIDFromCLSID互相转换。
-
COM组件的注册
COM组件的注册可以使用RegSvr32, 但是组件必须有DllRegisterServer和DllUnregisterServer。
注册时使用RegSvr32 aa.dll 反注册时使用RegSvr32 /u aa.dll
对于进程外组件,为了支持自注册必须支持/RegSever和/UnregSever两个参数。
类厂
类厂就是COM类的工厂,COM库通过类厂创建COM对象。IClassFactory是类厂的基类。定义如下:
IClassFactory : public IUnknown
{
public:
virtual /* [local] */ HRESULT STDMETHODCALLTYPE CreateInstance(
/* [unique][in] */ IUnknown *pUnkOuter,
/* [in] */ REFIID riid,
/* [iid_is][out] */ void **ppvObject) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE LockServer(
/* [in] */ BOOL fLock) = 0;
};
IClassFactory中成员函数CreateInstance负责创建对应的COM对象。
COM进程外类厂代码实现:
// ComFactory.h
#ifndef __COMFACTORY__
#define __COMFACTORY__
#include <Unknwn.h>
class CCOMFactory : public IClassFactory
{
public:
CCOMFactory();
~CCOMFactory();
//IUnknown members
HRESULT __stdcall QueryInterface(const IID& iid, void **ppv);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
//IClassFactory members
HRESULT __stdcall CreateInstance(IUnknown *, const IID& iid, void **ppv);
HRESULT __stdcall LockServer(BOOL);
public:
static BOOL RegisterFactory();
static void UnregisterFactory();
static BOOL CanUnloadNow();
public:
static CCOMFactory *theFactory;
static DWORD dwRegister;
protected:
LONG m_Ref;
};
#endif // __COMFACTORY__
// ComFactory.cpp
#include "ComFactory.h"
#include "CComInterface.h"
extern LONG g_lockNumber;
extern LONG g_comTestNumber;
extern DWORD g_dwMainThreadID;
extern "C" const GUID CLSID_COMTest;
CCOMFactory::CCOMFactory() : m_Ref(0)
{
}
CCOMFactory::~CCOMFactory()
{
}
HRESULT CCOMFactory::QueryInterface(const IID& iid, void **ppv)
{
if (iid == IID_IUnknown)
{
*ppv = (IUnknown *) this;
((IUnknown *)(*ppv))->AddRef();
}
else if (iid == IID_IClassFactory)
{
*ppv = (IClassFactory *) this;
((IClassFactory *)(*ppv))->AddRef();
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
ULONG CCOMFactory::AddRef()
{
return InterlockedIncrement(&m_Ref);
}
ULONG CCOMFactory::Release()
{
if (InterlockedDecrement(&m_Ref) == 0)
{
delete this;
return 0;
}
return (ULONG)m_Ref;
}
HRESULT CCOMFactory::CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void **ppv)
{
CComInterface * pObj;
HRESULT hr;
*ppv = NULL;
hr = E_OUTOFMEMORY;
if (NULL != pUnknownOuter)
{
return CLASS_E_NOAGGREGATION;
}
pObj = CComInterface::GetVirtualObject();
if (NULL == pObj)
{
return hr;
}
hr = pObj->QueryInterface(iid, ppv);
if (hr != S_OK)
{
InterlockedDecrement(&g_comTestNumber);
delete pObj;
}
return hr;
}
HRESULT CCOMFactory::LockServer(BOOL bLock)
{
if (bLock)
{
InterlockedIncrement(&g_lockNumber);
}
else
{
InterlockedDecrement(&g_lockNumber);
if (CanUnloadNow())
{
}
}
return NOERROR;
}
CCOMFactory *CCOMFactory::theFactory = NULL;
DWORD CCOMFactory::dwRegister = 0;
BOOL CCOMFactory::RegisterFactory()
{
theFactory = new CCOMFactory();
theFactory->AddRef();
IUnknown *pUnkForFactory = (IUnknown *)theFactory;
HRESULT hr = ::CoRegisterClassObject(
CLSID_COMTest,
pUnkForFactory,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&dwRegister);
if (FAILED(hr))
{
theFactory->Release();
return FALSE;
}
return TRUE;
}
void CCOMFactory::UnregisterFactory()
{
if (dwRegister != 0)
{
::CoRevokeClassObject(dwRegister);
}
if (theFactory != NULL)
{
theFactory->Release();
}
}
BOOL CCOMFactory::CanUnloadNow()
{
if (g_lockNumber > 0 || g_comTestNumber > 0)
{
return FALSE;
}
else
{
return TRUE;
}
}
在CreateInstance函数中通过CComInterface::GetVirtualObject()单例获取COM对象,并初始化了ppv指针。上述代码中有一些COM对象创建的代码和生命周期控制的代码。在下面的文章会提及。
在工厂类创建完后客户因该如何利用这个工厂类来得到COM对象呢,客户端可以利用CoGetClassObject、CoCreateInstance和CoCreateInstanceEx来创建对象。CoCreateInstance是对CoGetClassObject进行了封装使客户对COM工厂类透明,CoCreateInstanceEx可用来创建远程的COM对象。
![客户程序和组件工厂交互过程](https://imgkr2.cn-bj.ufileos.com/2de88705-75f1-403e-8008-d28ede1d1971.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=k3p5OZ%252Bvf91IZLVZzoeeKbkxGxE%253D&Expires=1608556068)
COM初始化和释放需要使用CoInitialize和CoUninitialize函数。
进程外组件的启动
当客户调用CoCreateInstance函数时,COM会从注册表获取组件的完整路径并检查组件程序是否启动,若程序为启动则COM库则会启动程序,若程序已经启动则直接执行对象的创建。
所以进程外组件需要实现组件的自注册到注册表,并且将类厂也注册到注册表。代码实现如下:
// AutoRegistry.h
#ifndef __Registry_H__
#define __Registry_H__
HRESULT RegisterServer(const CLSID& clsid,
const char *szFileName,
const char* szProgID,
const char* szDescription,
const char* szVerIndProgID);
HRESULT UnregisterServer(const CLSID& clsid,
const char* szProgID,
const char* szVerIndProgID);
#endif
// AutoRegistry.cpp
#include <objbase.h>
#include <assert.h>
#include "AutoRegistry.h"
BOOL SetKeyAndValue(const char* pszPath,
const char* szSubkey,
const char* szValue);
void CLSIDtoString(const CLSID& clsid,
char* szCLSID,
int length);
LONG DeleteKey(HKEY hKeyParent, const char* szKeyString);
const int CLSID_STRING_SIZE = 39;
HRESULT RegisterServer(const CLSID& clsid,
const char *szFileName,
const char* szProgID,
const char* szDescription,
const char* szVerIndProgID)
{
char szCLSID[CLSID_STRING_SIZE];
CLSIDtoString(clsid, szCLSID, sizeof(szCLSID));
char szKey[64];
strcpy_s(szKey, _countof("CLSID\\"), "CLSID\\");
strcat_s(szKey, _countof(szKey), szCLSID);
SetKeyAndValue(szKey, NULL, szDescription);
SetKeyAndValue(szKey, "LocalServer32", szFileName);
if (szProgID != NULL) {
SetKeyAndValue(szKey, "ProgID", szProgID);
SetKeyAndValue(szProgID, "CLSID", szCLSID);
}
if (szVerIndProgID) {
SetKeyAndValue(szKey, "VersionIndependentProgID",
szVerIndProgID);
SetKeyAndValue(szVerIndProgID, NULL, szDescription);
SetKeyAndValue(szVerIndProgID, "CLSID", szCLSID);
SetKeyAndValue(szVerIndProgID, "CurVer", szProgID);
SetKeyAndValue(szProgID, NULL, szDescription);
SetKeyAndValue(szProgID, "CLSID", szCLSID);
}
return S_OK;
}
HRESULT UnregisterServer(const CLSID& clsid,
const char* szProgID,
const char* szVerIndProgID)
{
char szCLSID[CLSID_STRING_SIZE];
CLSIDtoString(clsid, szCLSID, sizeof(szCLSID));
char szKey[64];
strcpy_s(szKey, _countof("CLSID\\"), "CLSID\\");
strcat_s(szKey, _countof(szCLSID), szCLSID);
LONG lResult = DeleteKey(HKEY_CLASSES_ROOT, szKey);
if (szVerIndProgID != NULL)
lResult = DeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID);
if (szProgID != NULL)
lResult = DeleteKey(HKEY_CLASSES_ROOT, szProgID);
return S_OK;
}
void CLSIDtoString(const CLSID& clsid,
char* szCLSID,
int length)
{
assert(length >= CLSID_STRING_SIZE);
LPOLESTR wszCLSID = NULL;
HRESULT hr = StringFromCLSID(clsid, &wszCLSID);
assert(SUCCEEDED(hr));
size_t ret;
wcstombs_s(&ret, szCLSID, length, wszCLSID, length);
CoTaskMemFree(wszCLSID);
}
LONG DeleteKey(HKEY hKeyParent, // Parent of key to delete
const char* lpszKeyChild) // Key to delete
{
// Open the child.
HKEY hKeyChild;
size_t dsize = strlen(lpszKeyChild) + 1;
wchar_t* dest = new wchar_t[dsize];
size_t i;
mbstowcs_s(&i, dest, dsize, lpszKeyChild, strlen(lpszKeyChild));
LONG lRes = RegOpenKeyEx(hKeyParent, dest, 0,
KEY_ALL_ACCESS, &hKeyChild);
delete[] dest;
if (lRes != ERROR_SUCCESS)
{
return lRes;
}
FILETIME time;
char szBuffer[256];
DWORD dwSize = 256;
while (RegEnumKeyExA(hKeyChild, 0, szBuffer, &dwSize, NULL,
NULL, NULL, &time) == S_OK)
{
lRes = DeleteKey(hKeyChild, szBuffer);
if (lRes != ERROR_SUCCESS)
{
RegCloseKey(hKeyChild);
return lRes;
}
dwSize = 256;
}
RegCloseKey(hKeyChild);
return RegDeleteKeyA(hKeyParent, lpszKeyChild);
}
BOOL SetKeyAndValue(const char* szKey,
const char* szSubkey,
const char* szValue)
{
HKEY hKey;
char szKeyBuf[1024];
strcpy_s(szKeyBuf, strlen(szKey) + 1, szKey);
if (szSubkey != NULL)
{
strcat_s(szKeyBuf, _countof(szKeyBuf), "\\");
strcat_s(szKeyBuf, _countof(szKeyBuf), szSubkey);
}
size_t dsize = strlen(szKeyBuf) + 1;
wchar_t* dest = new wchar_t[dsize];
size_t i;
mbstowcs_s(&i, dest, dsize, szKeyBuf, strlen(szKeyBuf));
long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT,
dest,
0, NULL, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL,
&hKey, NULL);
delete[] dest;
if (lResult != ERROR_SUCCESS)
{
return FALSE;
}
// Set the Value.
if (szValue != NULL)
{
RegSetValueExA(hKey, NULL, 0, REG_SZ,
(BYTE *)szValue,
strlen(szValue) + 1);
}
RegCloseKey(hKey);
return TRUE;
}
注意执行注册需要有管理员权限才能写入注册表。
COM回调接口的实现
在COM中要实现COM服务和客户程序的双向通信就需要COM回调接口。COM回调接口和回调函数类似但是COM不支持回调函数所以我们只能使用回调接口或者使用连接点。本文章主要讲解回调接口的实现。
首先我们定义回调接口
MIDL_INTERFACE("8A26B267-20B6-48BE-A6A1-7E36B2074DD3")
ICallBack : public IUnknown
{
public:
virtual HRESULT __stdcall SendMsgToClinet(long port) = 0;
};
这个回调接口很简单只是传给客户一个long值。
接着我们定义我们的COM对象接口
MIDL_INTERFACE("7799FC42-7048-4FDD-8035-DC1872A9F95E")
IComInterface : public IUnknown
{
public:
virtual HRESULT __stdcall Write(long size) = 0; // COM对象成员函数
virtual HRESULT __stdcall Read(long* port) = 0; // COM对象成员函数
virtual HRESULT __stdcall SetCallBack(ICallBack* pCallBack, long* callBackID) = 0;
virtual HRESULT __stdcall ReleaseCallBack(long callBackID) = 0; //释放ICallBack接口
virtual HRESULT __stdcall Test(void) = 0; //测试函数
};
SetCallBack函数将ICallBack接口给COM组件并返回一个标识符callBackID用于释放ICallBack接口。
//IComInterface.h
#ifndef __IComInterface_h__
#define __IComInterface_h__
#include <Unknwn.h>
#include <OAIdl.h>
#include "rpc.h"
#include "rpcndr.h"
#define COM_DLL_EXPORTS
#ifdef COM_DLL_EXPORTS
# define COM_DLL_API __declspec(dllexport)
#else
# define COM_DLL_API __declspec(dllimport)
#endif
#pragma warning( disable: 4049 )
#ifndef __ICallBack_FWD_DEFINED__
#define __ICallBack_FWD_DEFINED__
typedef interface ICallBack ICallBack;
#endif
#ifndef __IComInterface_FWD_DEFINED__
#define __IComInterface_FWD_DEFINED__
typedef interface IComInterface IComInterface;
#endif
#ifdef __cplusplus
typedef class CComInterface CComInterface;
#else
typedef struct CComInterface CComInterface;
#endif
#ifdef __cplusplus
extern "C"{
#endif
// {A83B5F67-1530-4A1B-AB2F-89B99E653335}
const GUID CLSID_COMTest = { 0xa83b5f67, 0x1530, 0x4a1b,
{ 0xab, 0x2f, 0x89, 0xb9, 0x9e, 0x65, 0x33, 0x35 } };
// {7799FC42-7048-4FDD-8035-DC1872A9F95E}
const GUID IID_IComInterface = { 0x7799fc42, 0x7048, 0x4fdd,
{ 0x80, 0x35, 0xdc, 0x18, 0x72, 0xa9, 0xf9, 0x5e } };
// {8A26B267-20B6-48BE-A6A1-7E36B2074DD3}
const GUID IID_CallBack = { 0x8a26b267, 0x20b6, 0x48be,
{ 0xa6, 0xa1, 0x7e, 0x36, 0xb2, 0x7, 0x4d, 0xd3 } };
/// {9BBD854F-970A-4ABD-917E-FF736C8C86CB}
const GUID IID_IComInterfaceLib = { 0x9bbd854f, 0x970a, 0x4abd,
{ 0x91, 0x7e, 0xff, 0x73, 0x6c, 0x8c, 0x86, 0xcb } };
// {6779B5C9-3F74-47CA-8800-5DC8A7F9FD55}
const GUID IID_IComInterfaceObject = { 0x6779b5c9, 0x3f74, 0x47ca,
{ 0x88, 0x0, 0x5d, 0xc8, 0xa7, 0xf9, 0xfd, 0x55 } };
MIDL_INTERFACE("8A26B267-20B6-48BE-A6A1-7E36B2074DD3")
ICallBack : public IUnknown
{
public:
virtual HRESULT __stdcall SendMsgToClinet(long port) = 0;
};
MIDL_INTERFACE("7799FC42-7048-4FDD-8035-DC1872A9F95E")
IComInterface : public IUnknown
{
public:
virtual HRESULT __stdcall Write(long size) = 0;
virtual HRESULT __stdcall Read(long* port) = 0;
virtual HRESULT __stdcall SetCallBack(ICallBack* pCallBack, long* callBackID) = 0;
virtual HRESULT __stdcall ReleaseCallBack(long callBackID) = 0;
virtual HRESULT __stdcall Test(void) = 0;
};
class DECLSPEC_UUID("6779B5C9-3F74-47CA-8800-5DC8A7F9FD55") CComInterface;
#ifdef __cplusplus
}
#endif
#endif
// CComInterface.h
#pragma once
#include "IComInterface.h"
#include <map>
class CComInterface : public IComInterface
{
public:
static CComInterface* GetVirtualObject();
~CComInterface();
private:
CComInterface();
public:
// IUnknown member function
virtual HRESULT __stdcall QueryInterface(const IID& iid, void **ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
// IVirtualChannel member function
virtual HRESULT __stdcall Write(long size);
virtual HRESULT __stdcall Read(long* port);
virtual HRESULT __stdcall SetCallBack(ICallBack* pCallBack, long* callBackID);
virtual HRESULT __stdcall ReleaseCallBack(long callBackID);
virtual HRESULT __stdcall Test(void);
private:
static CComInterface* m_obj;
LONG m_ref{ 0 }; // 组件引用计数
std::map<int, ICallBack *> m_pCalls;
};
//CComInterface.cpp
#include "CComInterface.h"
#include "ComFactory.h"
#include <stdio.h>
LONG g_lockNumber = 0;
LONG g_comTestNumber = 0;
DWORD g_dwMainThreadID = 0;
LONG g_ser = 0;
CComInterface* CComInterface::m_obj = NULL;
CComInterface* CComInterface::GetVirtualObject()
{
if (m_obj == NULL)
{
m_obj = new CComInterface();
}
return m_obj;
}
CComInterface::~CComInterface()
{
if (CCOMFactory::CanUnloadNow())
{
// TODO: 结束COM
}
}
CComInterface::CComInterface()
{
InterlockedIncrement(&g_comTestNumber);
}
HRESULT CComInterface::QueryInterface(const IID& iid, void **ppv)
{
if (iid == IID_IUnknown)
{
*ppv = (IComInterface *) this;
((IComInterface *)(*ppv))->AddRef();
}
else if (iid == IID_IComInterface)
{
*ppv = (IComInterface *) this;
((IComInterface *)(*ppv))->AddRef();
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
ULONG CComInterface::AddRef()
{
return InterlockedIncrement(&m_ref);
}
ULONG CComInterface::Release()
{
if (InterlockedDecrement(&m_ref) == 0) {
InterlockedDecrement(&g_comTestNumber);
delete this;
return 0;
}
return (ULONG)m_ref;
}
HRESULT CComInterface::Write(long size)
{
printf("Write %d", size);
return S_OK;
}
HRESULT CComInterface::Read(long* port)
{
*port = 10;
printf("Read %d", *port);
return S_OK;
}
HRESULT CComInterface::SetCallBack(ICallBack* pCallBack, long* callBackID)
{
printf("pCallBack\n");
if (pCallBack == NULL)
{
return S_FALSE;
}
*callBackID = g_ser;
m_pCalls[g_ser] = pCallBack;
g_ser++;
ULONG ref = pCallBack->AddRef();
return S_OK;
}
HRESULT CComInterface::ReleaseCallBack(long callBackID)
{
printf("ReleaseCallBack\n");
auto& it = m_pCalls.find(callBackID);
if (it != m_pCalls.end())
{
ICallBack* back = it->second;
back->Release();
back = NULL;
m_pCalls.erase(it);
return S_OK;
}
return S_FALSE;
}
HRESULT CComInterface::Test(void)
{
printf("Test\n");
for (auto&it : m_pCalls)
{
it.second->SendMsgToClinet(3);
}
return S_OK;
}
在CComInterface.h 文件定义m_pCalls map字典用于存放客户注册的回调接口,当调用SetCallBack函数时将ICallBack接口放入m_pCalls,当有事件触发时依次调用接口。如Test函数做的一样。
至此整个进程外组件已经实现完毕。
代理的实现
idl文件编写
import "unknwn.idl";
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(8A26B267-20B6-48BE-A6A1-7E36B2074DD3),
helpstring("ICallBack 接口"),
pointer_default(unique)
]
interface ICallBack : IUnknown
{
[helpstring("方法SendMsgToClinet")]
HRESULT SendMsgToClinet([in] long port) = 0;
};
[
object,
uuid(7799FC42-7048-4FDD-8035-DC1872A9F95E),
helpstring("IComInterface 接口"),
pointer_default(unique)
]
interface IComInterface : IUnknown
{
[helpstring("方法Write")]
HRESULT Write( [in] long size) = 0;
[helpstring("方法Read")]
HRESULT Read([out] long* port) = 0;
[helpstring("方法SetCallBack")]
HRESULT SetCallBack([in]ICallBack* pCallBack, [out]long* callBackID) = 0;
[helpstring("方法ReleaseCallBack")]
HRESULT ReleaseCallBack([in]long callBackID) = 0;
[helpstring("方法Test")]
HRESULT Test(void) = 0;
};
[
uuid(9BBD854F-970A-4ABD-917E-FF736C8C86CB),
version(1.0),
helpstring("IComInterfaceLib 1.0 类型库")
]
library IComInterfaceLib
{
importlib("stdole2.tlb");
[
uuid(6779B5C9-3F74-47CA-8800-5DC8A7F9FD55),
helpstring("IComInterfaceLib Class")
]
coclass CComInterface
{
[default] interface IComInterface;
};
};
LIBRARY "COMProxy"
EXPORTS
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
GetProxyDllInfo PRIVATE
新建一个DLL项目并添加idl和def文件后开始编译,这时会报错。不用担心我们需要对项目的属性做些设置。
-
预处理修改
如图在预处理中添加DICTPRXY_EXPORTS,REGISTER_PROXY_DLL,点击确定
-
附加依赖项 如图:附加依赖项中添加rpcrt4.lib,Rpcns4.lib,点击确定
再次编译项目发行成功生成DLL。
使用regSvr32 COMProxy注册到注册表。
客户端程序编写
-
回调接口类实现
//Sink.h
//回调实例类
class CSink :public ICallBack
{
protected:
ULONG m_cRef;
public:
CSink(void);
~CSink(void);
STDMETHOD(QueryInterface)(const struct _GUID &iid,void ** ppv);
ULONG __stdcall AddRef(void);
ULONG __stdcall Release(void);
HRESULT __stdcall SendMsgToClinet(long port);
};
//Sink.cpp
#include "sink.h"
#include <stdio.h>
#include <stdint.h>
CSink::CSink(void) :m_cRef(0)
{
}
CSink::~CSink(void)
{
}
STDMETHODIMP CSink::QueryInterface(const struct _GUID &iid, void ** ppv)
{
*ppv = NULL;
if (IID_IUnknown == iid || IID_CallBack == iid)
{
*ppv = this;
}
if (NULL != *ppv)
{
((LPUNKNOWN)*ppv)->AddRef();
return S_OK;
}
return ResultFromScode(E_NOINTERFACE);
}
ULONG __stdcall CSink::AddRef()
{
return InterlockedIncrement(&m_cRef);;
}
ULONG __stdcall CSink::Release()
{
if (InterlockedDecrement(&m_cRef) == 0)
{
delete this;
return 0;
}
return (ULONG)m_cRef;
}
HRESULT __stdcall CSink::SendMsgToClinet(long port)
{
printf("wwwww port=%d\n", port);
return S_OK;
}
CSink 继承了ICallBack接口实现ICallBack接口。
-
客户进程调用COM对象
#include <comutil.h>
#include <stdio.h>
#include <OAIdl.h>
#include "..\COMOut\IComInterface.h"
#include "Sink.h"
int main()
{
IUnknown *pUnknown = NULL;
IComInterface *pIComInterface = NULL;
HRESULT hResult;
if (CoInitialize(NULL) != S_OK)
{
printf("Initialize COM library failed!\n");
Sleep(3000);
CoUninitialize();
return -1;
}
hResult = CoCreateInstance(CLSID_COMTest, NULL,
CLSCTX_LOCAL_SERVER, IID_IUnknown, (void **)&pUnknown);
if (FAILED(hResult))
{
printf("Create object failed!\n");
Sleep(3000);
CoUninitialize();
return -2;
}
hResult = pUnknown->QueryInterface(IID_IComInterface, (void **)&pIComInterface);
if (FAILED(hResult))
{
pUnknown->Release();
printf("QueryInterface IID_IComInterface failed hResult=%d erro=%d!\n", hResult, GetLastError());
Sleep(3000);
CoUninitialize();
return -3;
}
CSink *m_pSinkCallBack = new CSink();
m_pSinkCallBack->AddRef();
long backID;
pIComInterface->SetCallBack(m_pSinkCallBack, &backID);
while (true)
{
pIComInterface->Test();
Sleep(1000);
}
pIComInterface->Release();
CoUninitialize();
return 0;
}
至此COM回调接口已经完全实现了。
试验结果
项目目录结构:ComClient为客户进程、COMProxy为存根DLL、COMOut为进程外组件。
启动客户进程ComClient.exe,COMOut.exe随之启动并设置了回调接口和执行了回调函数。
结束语
此为手动实现COM各种细节,可以方便于改造旧程序。对于新开发的程序如果要使用COM进程外组件建议读者可利用Micsoft的框架来实现如ATL等。最后关注公众号“智子开天科技“ 回复“COM进程外组件”可获得源码哦。
![长按图片关注我哦,更多windows编程精彩内容~~~~](https://imgkr2.cn-bj.ufileos.com/af673f61-8e61-492b-a3ab-6d7206dae05e.jpg?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=gecdnqMtcmHfv1jI3uupZpZ%252BGhM%253D&Expires=1608559784)