COM初探(五)——从IDispatch接口继承
(一)目标
本文用一个简单的例子来讲述如何使我们的COM对象可以被脚本语言调用。
(二)IDL文件
为了在脚本语言环境中使用COM组件,COM规范规定要在脚本语言环境使用的COM必须实现IDispatch接口。
下面我们定义接口ITimeBeijing,请注意这个文件和我们以前的文件的不同点:
import "oaidl.idl";
import "ocidl.idl";
[
uuid(30002489-29D8-4c48-9B1F-D6660A818E7B),
object,
dual,//这个标识说明定义的是双接口
pointer_default(unique)
]
interface ITimeBeijing : IDispatch//必须从IDispatch接口派生
{
[id(1)] HRESULT GetHour([out, retval] int * hour);//每个函数前都有id号,便于调用
[id(2)] HRESULT GetMinute([out, retval] int * min);
[id(3)] HRESULT GetSecond([out, retval] int * sec);
};
[
uuid(8AE3C9BA-CEE9-4d33-A4C1-DB4FBFBB9A43),
version(1.0),
]
library TimeBeijingLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(ECE8BF15-11FB-45a4-90A1-1788DDAB31AC),
]
coclass TimeBeijingClass//com类
{
[default] interface ITimeBeijing;
};
};
(三)建立COM类TimeBeijingClass
请注意和以前的不同点。
这里的类需要实现3个接口:IUnknown, IDispatch和ITimeBeijing。
// TimeBeijingClass.h: interface for the TimeBeijingClass class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_TIMEBEIJINGCLASS_H__E9800212_16E2_4BE3_B646_420DC02F50AE__INCLUDED_)
#define AFX_TIMEBEIJINGCLASS_H__E9800212_16E2_4BE3_B646_420DC02F50AE__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "TimeBeijing.h"
class TimeBeijingClass : public ITimeBeijing
{
public:
TimeBeijingClass();
~TimeBeijingClass();
public:
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
// IDispatch
STDMETHOD(GetTypeInfoCount)(UINT * pit);
STDMETHOD(GetTypeInfo)(UINT it,LCID lcid,ITypeInfo **ppti);
STDMETHOD(GetIDsOfNames)(REFIID riid,
OLECHAR ** pNames,
UINT nNames,
LCID lcid,
DISPID * pdispids);
STDMETHOD(Invoke)(DISPID id,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pd,
VARIANT * pVarResult,
EXCEPINFO * pe,
UINT *pu);
// ITimeBeijing
STDMETHOD(GetHour)(int * hour);
STDMETHOD(GetMinute)(int * min);
STDMETHOD(GetSecond)(int * sec);
private:
LONG m_cRef;
};
#endif // !defined(AFX_TIMEBEIJINGCLASS_H__E9800212_16E2_4BE3_B646_420DC02F50AE__INCLUDED_)
// TimeBeijingClass.cpp: implementation of the TimeBeijingClass class.
//
//////////////////////////////////////////////////////////////////////
#include "TimeBeijingClass.h"
#include "stdio.h"
#include <time.h>
#include "TimeBeijing_i.c"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
TimeBeijingClass::TimeBeijingClass()
{
printf("TimeBeijingClass - Constructor\n");
m_cRef = 0;
}
TimeBeijingClass::~TimeBeijingClass()
{
printf("TimeBeijingClass - Destructor\n");
}
// IUnknown
STDMETHODIMP TimeBeijingClass::QueryInterface(REFIID riid, void **ppv)
{
printf("TimeBeijingClass - QueryInterface\n");
if(riid == IID_ITimeBeijing)
*ppv = static_cast<ITimeBeijing *>(this);
else if(riid == IID_IUnknown)
*ppv = static_cast<ITimeBeijing *>(this);
else if(riid == IID_IDispatch)
*ppv = static_cast<IDispatch *>(this);
else {
*ppv = 0;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown *>(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) TimeBeijingClass::AddRef()
{
printf("TimeBeijingClass - AddRef\n");
return ++m_cRef;
}
STDMETHODIMP_(ULONG) TimeBeijingClass::Release()
{
printf("TimeBeijingClass - Release\n");
ULONG res = --m_cRef;
if(res == 0)
delete this;
return res;
}
// IDispatch
//此函数是重点,请结合程序并参考msdn理解其中参数的含义
STDMETHODIMP TimeBeijingClass::Invoke(DISPID id, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS *pd,
VARIANT * pVarResult, EXCEPINFO * pe, UINT *pu)
{
if(riid == IID_ITimeBeijing)
{
if(1 == id)
return GetHour(&pVarResult->intVal);
else if (2 == id)
return GetMinute(&pVarResult->intVal);
else if (3 == id)
return GetSecond(&pVarResult->intVal);
else
return E_FAIL;
}
else
return E_NOINTERFACE;
}
//为了简化程序,下面的接口函数都没有实现
STDMETHODIMP TimeBeijingClass::GetIDsOfNames(REFIID riid, OLECHAR ** pNames,
UINT nNames, LCID lcid, DISPID * pdispids)
{
return E_NOTIMPL;
}
STDMETHODIMP TimeBeijingClass::GetTypeInfo(UINT it,LCID lcid,ITypeInfo **ppti)
{
return E_NOTIMPL;
}
STDMETHODIMP TimeBeijingClass::GetTypeInfoCount(UINT * pit)
{
return E_NOTIMPL;
}
// ITimeBeijing
STDMETHODIMP TimeBeijingClass::GetHour(int * hour)
{
printf("TimeBeijingClass - GetHour\n");
time_t ltime;
struct tm *today;
time(<ime);
today = localtime(<ime);
*hour = today->tm_hour;
return S_OK;
}
STDMETHODIMP TimeBeijingClass::GetMinute(int * min)
{
printf("TimeBeijingClass - GetMinute\n");
time_t ltime;
struct tm *today;
time(<ime);
today = localtime(<ime);
*min = today->tm_min;
return S_OK;
}
STDMETHODIMP TimeBeijingClass::GetSecond(int * sec)
{
printf("TimeBeijingClass - GetMinute\n");
time_t ltime;
struct tm *today;
time(<ime);
today = localtime(<ime);
*sec = today->tm_sec;
return S_OK;
}
(四)类工厂文件(微小的变化:QueryInterface)
// TimeBeijingClassFactory.h: interface for the TimeBeijingClassFactory class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_TIMEBEIJINGCLASSFACTORY_H__AB2637DD_45D2_48CD_BAD8_46E372268A8C__INCLUDED_)
#define AFX_TIMEBEIJINGCLASSFACTORY_H__AB2637DD_45D2_48CD_BAD8_46E372268A8C__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <unknwn.h>
class TimeBeijingClassFactory : public IClassFactory
{
public:
TimeBeijingClassFactory();
~TimeBeijingClassFactory();
public:
//IUnknow Method
STDMETHODIMP QueryInterface(REFIID,void**);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
//IClassFactory Method
STDMETHODIMP CreateInstance(IUnknown * ,REFIID ,void **);
STDMETHODIMP LockServer(BOOL fLock);
private:
LONG m_cRef;
};
#endif // !defined(AFX_TIMEBEIJINGCLASSFACTORY_H__AB2637DD_45D2_48CD_BAD8_46E372268A8C__INCLUDED_)
// TimeBeijingClassFactory.cpp: implementation of the TimeBeijingClassFactory class.
//
//////////////////////////////////////////////////////////////////////
#include "TimeBeijingClassFactory.h"
#include "stdio.h"
#include "TimeBeijingClass.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
extern LONG g_cObjectAndLocks;
TimeBeijingClassFactory::TimeBeijingClassFactory()
{
printf("TimeBeijingClassFactory - constructor\n");
m_cRef = 0;
}
TimeBeijingClassFactory::~TimeBeijingClassFactory()
{
printf("TimeBeijingClassFactory - destructor\n");
}
STDMETHODIMP_(ULONG) TimeBeijingClassFactory::AddRef(void)
{
printf("TimeBeijingClassFactory - AddRef\n");
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) TimeBeijingClassFactory::Release(void)
{
printf("TimeBeijingClassFactory - Release\n");
return ::InterlockedDecrement(&m_cRef);
}
STDMETHODIMP TimeBeijingClassFactory::QueryInterface(REFIID riid,void ** ppv)
{
printf("TimeBeijingClassFactory - QueryInterface\n");
*ppv = NULL;
if(riid == IID_IUnknown || riid == IID_IClassFactory || riid == IID_IDispatch)//变化在此!!!
{
*ppv = static_cast<IClassFactory *>(this);
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
else
return (*ppv=0),E_NOINTERFACE;
}
STDMETHODIMP TimeBeijingClassFactory::CreateInstance(IUnknown * pUnkOuter,REFIID riid,void ** ppv)
{
printf("TimeBeijingClassFactory - CreateInstance\n");
*ppv=NULL;
if(pUnkOuter != NULL)
return CLASS_E_NOAGGREGATION;
TimeBeijingClass * pTimeBeijingClass = new TimeBeijingClass;
if(pTimeBeijingClass == NULL)
return E_OUTOFMEMORY;
HRESULT hr = pTimeBeijingClass->QueryInterface(riid,ppv);
if(FAILED(hr))
delete pTimeBeijingClass;
return hr;
}
STDMETHODIMP TimeBeijingClassFactory::LockServer(BOOL fLock)
{
printf("TimeBeijingClassFactory - LockServer\n");
if(fLock)
::InterlockedIncrement(&g_cObjectAndLocks);
else
::InterlockedDecrement(&g_cObjectAndLocks);
return NOERROR;
}
(五)服务器主文件Server.cpp(没有变化)
#include <objbase.h>
#include <initguid.h>
#include "TimeBeijing.h"
#include "TimeBeijing_i.c"
#include "TimeBeijingClassFactory.h"
HINSTANCE g_hinstDll;//DLL实例句柄
LONG g_cObjectAndLocks;
const char * g_RegTable[][3]={//COM对象注册
{"CLSID\\{ECE8BF15-11FB-45a4-90A1-1788DDAB31AC}",
0,
"TimeBeijing"},
{"CLSID\\{ECE8BF15-11FB-45a4-90A1-1788DDAB31AC}\\InprocServer32",
0,
(const char * )-1},
{"CLSID\\{ECE8BF15-11FB-45a4-90A1-1788DDAB31AC}\\ProgID",0,"Justin.TimeBeijing.3"},
{"Justin.TimeBeijing.3",0,"TimeBeijing"},
{"Justin.TimeBeijing.3\\CLSID",0,"{ECE8BF15-11FB-45a4-90A1-1788DDAB31AC}"},
};
//DLL入口
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
g_hinstDll = (HINSTANCE) hModule;//保存句柄
return TRUE;
}
STDAPI DllUnregisterServer(void)//从注册表中注销
{
HRESULT hr = S_OK;
char szFileName [MAX_PATH];
::GetModuleFileName(g_hinstDll,szFileName,MAX_PATH);
int nEntries = sizeof(g_RegTable) / sizeof(*g_RegTable);
for(int i = 0; SUCCEEDED(hr) && i < nEntries; i ++)
{
const char * pszKeyName = g_RegTable[i][0];
long err = ::RegDeleteKey(HKEY_CLASSES_ROOT, pszKeyName);
if( err != ERROR_SUCCESS)
hr = S_FALSE;
}
return hr;
}
STDAPI DllRegisterServer(void)//注册服务器
{
HRESULT hr = S_OK;
char szFileName [MAX_PATH];
::GetModuleFileName(g_hinstDll, szFileName, MAX_PATH);
int nEntries = sizeof(g_RegTable) / sizeof(*g_RegTable);
for(int i = 0 ; SUCCEEDED(hr) && i < nEntries; i ++)
{
const char * pszKeyName = g_RegTable[i][0];
const char * pszValueName = g_RegTable[i][1];
const char * pszValue = g_RegTable[i][2];
if(pszValue == (const char *) - 1)
{
pszValue = szFileName;
}
HKEY hkey;
long err = ::RegCreateKey(HKEY_CLASSES_ROOT, pszKeyName, &hkey);
if(err == ERROR_SUCCESS)
{
err = ::RegSetValueEx( hkey,
pszValueName,
0,
REG_SZ,
( const BYTE*)pszValue,
( strlen(pszValue)+1 ) );
::RegCloseKey(hkey);
}
if( err != ERROR_SUCCESS)
{
::DllUnregisterServer();
hr = E_FAIL;
}
}
return hr;
}
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv)
{
if(rclsid == CLSID_TimeBeijingClass)
{
TimeBeijingClassFactory *pFactory = new TimeBeijingClassFactory;
if(pFactory == NULL)
return E_OUTOFMEMORY;
HRESULT hr = pFactory->QueryInterface(riid, ppv);
return hr;
}
return CLASS_E_CLASSNOTAVAILABLE;
}
STDAPI DllCanUnloadNow(void)
{
return ( g_cObjectAndLocks == 0 )
? S_OK : E_FAIL;
}
(六)DEF文件(没有变化)
LIBRARY "TimeBeijing.DLL"
EXPORTS
DllCanUnloadNow @1 PRIVATE
DllGetClassObject @2 PRIVATE
DllRegisterServer @3 PRIVATE
DllUnregisterServer @4 PRIVATE
编译链接上述文件,得到了服务器端程序。
让我们启动服务器:regsvr32 TimeBeijing.DLL (回车)
(七)vc客户端
下面进入测试阶段,首先,我们建立vc客户端进行测试。
// TEST.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "TEST.h"
#include "../TimeBeijing_i.c"
#include "atlbase.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// The one and only application object
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
cout << "Initialize COM Runtime......";
HRESULT hr = ::CoInitialize(NULL);
if(SUCCEEDED(hr))
cout << "ok" << endl;
else
{
cout << "failed" << endl;
CoUninitialize();
system("pause");
exit(1);
}
IDispatch *pDispatch = NULL;//IDispatch 接口指针
cout << "Get IDispatch Interface Pointer......";
hr = ::CoCreateInstance(CLSID_TimeBeijingClass,
NULL,
CLSCTX_INPROC,
IID_IDispatch,
(void**)&pDispatch);
if(SUCCEEDED(hr))
cout << "ok" << endl;
else
{
cout << "failed" << endl;
CoUninitialize();
system("pause");
exit(1);
}
cout << "Now use IDispatch Interface..." << endl;
CComVariant varResult1;
CComVariant varResult2;
CComVariant varResult3;
varResult1.Clear();
varResult2.Clear();
varResult3.Clear();
cout << "Use Method ID.1......";
hr = pDispatch->Invoke(0x1, // ID 1
IID_ITimeBeijing,
LOCALE_USER_DEFAULT,
DISPATCH_METHOD,
NULL,
&varResult1,
NULL,
NULL);
if(SUCCEEDED(hr))
cout << "ok" << endl;
else
{
cout << "failed" << endl;
pDispatch->Release();
CoUninitialize();
system("pause");
exit(1);
}
cout << "Use Method ID.2......";
hr = pDispatch->Invoke(0x2, // ID 2
IID_ITimeBeijing,
LOCALE_USER_DEFAULT,
DISPATCH_METHOD,
NULL,
&varResult2,
NULL,
NULL);
if(SUCCEEDED(hr))
cout << "ok" << endl;
else
{
cout << "failed" << endl;
pDispatch->Release();
CoUninitialize();
system("pause");
exit(1);
}
cout << "Use Method ID.3......";
hr = pDispatch->Invoke(0x3, // ID 1
IID_ITimeBeijing,
LOCALE_USER_DEFAULT,
DISPATCH_METHOD,
NULL,
&varResult3,
NULL,
NULL);
if(SUCCEEDED(hr))
cout << "ok" << endl;
else
{
cout << "failed" << endl;
pDispatch->Release();
CoUninitialize();
system("pause");
exit(1);
}
printf("Time NOW : %d - %d - %d \n", varResult1.intVal,
varResult2.intVal, varResult3.intVal);
cout << "Close COM Runtime......" <<endl;
pDispatch->Release();
::CoUninitialize();
system("pause");
return 0;
}
测试结果:
Initialize COM Runtime......ok
Get IDispatch Interface Pointer......TimeBeijingClassFactory - constructor
TimeBeijingClassFactory - QueryInterface
TimeBeijingClassFactory - AddRef
TimeBeijingClassFactory - CreateInstance
TimeBeijingClass - Constructor
TimeBeijingClass - QueryInterface
TimeBeijingClass - AddRef
TimeBeijingClass - AddRef
TimeBeijingClass - Release
TimeBeijingClassFactory - Release
TimeBeijingClass - QueryInterface
TimeBeijingClass - AddRef
TimeBeijingClass - Release
ok
Now use IDispatch Interface...
Use Method ID.1......TimeBeijingClass - GetHour
ok
Use Method ID.2......TimeBeijingClass - GetMinute
ok
Use Method ID.3......TimeBeijingClass - GetMinute
ok
Time NOW : 14 - 37 - 50
Close COM Runtime......
TimeBeijingClass - Release
TimeBeijingClass - Destructor
请按任意键继续 . . .
(八)vb客户端
建立带有一个按钮的Standard EXE工程。
注意选中Project --> References -->TimeBeijingLib
输入下列代码:
Private Sub Command1_Click()
Dim c As TimeBeijingClass
Dim h As Integer
Dim m As Integer
Dim s As Integer
Set c = New TimeBeijingClass
h = c.GetHour
m = c.GetMinute
s = c.GetSecond
Print "Time NOW: "; h; " - "; m; " - "; s
End Sub
显示结果如下:
Time NOW: 20 - 6 - 34