不用ATL框架纯手工实现COM进程外回调

不用ATL框架纯手工实现COM进程外回调

COM全称为(Component Object Model)组件对象模型。COM是由Microcsoft提出的组件标准,它定义了组件程序之间进行交互的标准。COM组件可分为进程内组件和进程外组件。本编文章主要讲述进程外组件的回调实现。

COM进程外组件模型

COM进程外组件是以独立进程的形式向客户提供对象服务的,客户调用组件程序提供的服务,必然要跨进程调用。在COM中这是通过代理存根来实现的。

客户进程调用COM进程外组件的过程主要为以下6步:

  1. 客户进程调用COM接口函数
  2. 代理对象通过LPC调用服务程序中的存根DLL
  3. 在服务进程中的存根DLL调用COM组件对象提供的接口
  4. COM组件对象接口执行完毕返回结果给存根DLL
  5. 存根DLL将结果通过LPC传递给客户进程中代理DLL
  6. 在客户进程中的代理DLL再将结果返回给客户

具体调用过程如下图: COM进程外组件接口调用

客户进程调用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,每个项下面又有若干子项。
注册表
注册表
  • COM组件注册信息 COM组件的注册信息在HKEY_CLASSES_ROOT键下,最主要的信息在CLSID键下,该键下列出了当前系统上所有的组件信息。如下图: CLSID

在每个CLSID子键下包含了组件对象相关的信息,如进程内组件CLSID子键下包含了InprocServer32子键该键的缺省值为组件的全路径名。进程外组件则包含LocalServer32子键该键的缺省值为进程外组件的全路径名。如下图: 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对象。

客户程序和组件工厂交互过程
客户程序和组件工厂交互过程

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,
  NULLNULL, &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,
  0NULL, 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, NULL0, 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编程精彩内容~~~~
长按图片关注我哦,更多windows编程精彩内容~~~~
- END -
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值