不用ATL框架純手工實現COM程式外回撥
不用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,每個項下面又有若干子項。
-
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物件。
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程式外元件”可獲得原始碼哦。
- END -相關文章
- Android 回撥方法的實現Android
- 用Kotlin實現極簡回撥Kotlin
- VC++、MFC、COM和ATL的區別C++
- 【JavaSE】java實現閉包與回撥Java
- C語言回撥日誌庫的實現C語言
- 用匿名內部類實現 Java 同步回撥Java
- Swift 中如何利用閉包實現非同步回撥?Swift非同步
- kPagination-純js實現分頁外掛JS
- Layui三方外掛OPTable的回撥UI
- [JS]回撥函式和回撥地獄JS函式
- 基於Guava API實現非同步通知和事件回撥GuavaAPI非同步事件
- 回撥方法
- 退避演算法實現之客戶端優雅回撥演算法客戶端
- Activity生命週期回撥是如何被回撥的?
- 非同步/回撥非同步
- js 回撥 callbackJS
- 回撥函式函式
- 回撥地獄
- C++回撥C++
- java回撥函式-非同步回撥-簡明講解Java函式非同步
- [iOS] [OC] 關於block回撥、高階函式“回撥再呼叫”及專案實踐iOSBloC函式
- C#使用委託實現函式回撥,方法呼叫攔截C#函式
- 不用for迭代 --手工訪問迭代器中的元素.
- chrome + vi 純手工模式, 廢棄滑鼠。Chrome模式
- 事實上,回撥函式還不錯!!函式
- uni-app 中實現 onLaunch 非同步回撥後執行 onLoad 最佳實踐APP非同步
- ajax--實現非同步請求,接受響應及執行回撥非同步
- Swift如何純程式碼實現時鐘效果Swift
- JavaScript 回撥函式JavaScript函式
- 微博回撥介面
- JavaScript回撥函式JavaScript函式
- JS—回撥函式JS函式
- 回撥函式(CallBack)函式
- 【詳細、開箱即用】.NET企業微信回撥配置(資料回撥URL和指令回撥URL驗證)
- xm外匯中如何判斷回撥趨勢的方法有哪些?
- 【純手工打造】時間戳轉換工具(python)時間戳Python
- 小程式生命週期分析與註冊流程回撥
- 如何避免回撥地獄