COM初探(二)
(一)目标
本文对“COM初探(一)”加以改进,建立了一个自注册的进程内(inproc)DLL服务器。程序功能是得到当前的北京时间。
(二)IDL(接口定义)文件的手动建立
使用文本编辑器建立接口定义文件TimeBeijing.idl,此文件中定义了需要的COM接口,COM类和COM库。
文件内容如下:
import "oaidl.idl";//导入必要的idl文件
import "ocidl.idl";
[
uuid(61CD1D80-1FA6-434e-AFD6-BE2053C66425),// COM全局标识符,唯一确定一个对象或者接口
helpstring("ITimeBeijing Interface"),//帮助文字
object, //表明是COM对象
pointer_default(unique)//对所有的内嵌指针指定一个默认指针属性
]
interface ITimeBeijing : IUnknown //接口ITimeBeijing从IUnknown派生
{
cpp_quote("//获取当前小时数")
HRESULT GetHour([out, retval]int *hour);
cpp_quote("//获取当前分钟数")
HRESULT GetMinute([out, retval]int *minute);
cpp_quote("//获取当前秒数")
HRESULT GetSecond([out, retval]int *second);
};
[
uuid(4E4FE340-5E3E-4e6f-93D3-217714663B5B),
version(1.0),
helpstring("TimeBeijing 1.0 Type Library")
]
library TimeBeijingLib//TimeBeijingLib COM库
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(F9A47C8D-A7E4-477b-A27A-47024DE647ED),
helpstring("MyTimeBeijing Class")
]
coclass MyTimeBeijing//COM类 MyTimeBeijing
{
[default] interface ITimeBeijing;
};
};
(三)使用MIDL.exe编译IDL文件
在命令行下敲入:
midl TimeBeijing.dil (回车)
可以得到以下的5个文件:
TimeBeijing.h 此文件中包含了接口的c++定义
TimeBeijing_i.c 此文件中包含了IID和CLSID的定义
TimeBeijing_p.c 此文件中包含了proxy/stub(代理/存根)代码
dllData.c 记不清了,好像是用于Malshelling/Unmalshelling(列集)
TimeBeijing.tlb Type Library,类型库,可以用于脚本语言,如vb
使用vc带的工具OLE/COM Object Viewer可以打开TimeBeijing.tlb进行察看。
(四)建立DLL服务器,进行自动注册和注销等等
1)从TimeBeijing.h中的接口定义派生COM类MyTimeBeijing
// MyTimeBeijing.h
#pragma once
#include "TimeBeijing.h"
class MyTimeBeijing : public ITimeBeijing //实现了接口的类MyTimeBeijing
{
public:
MyTimeBeijing();
~MyTimeBeijing();
private:
ULONG m_cRef; //引用计数
public://以下实现接口
//IUnknown接口
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
//ITimeBeijing接口
STDMETHOD(GetHour)(int* hour);
STDMETHOD(GetMinute)(int *minute);
STDMETHOD(GetSecond)(int* second);
};
//MyTimeBeijing.cpp
#include "stdio.h"
#include "MyTimeBeijing.h"
#include <time.h>
MyTimeBeijing::MyTimeBeijing()
{
printf("Constructor called.../n");
m_cRef = 0;
}
MyTimeBeijing::~MyTimeBeijing()
{
printf("Destructor called.../n");
}
STDMETHODIMP MyTimeBeijing::QueryInterface(REFIID riid, void **ppv)
{
printf("QueryInterface called.../n");
if(riid == IID_ITimeBeijing) //判断接口类型
*ppv = static_cast<ITimeBeijing*>(this);
else if(riid == IID_IUnknown)
*ppv = static_cast<IUnknown*>(this);
else
{
*ppv = 0;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef(); //进行计数
return S_OK;
}
STDMETHODIMP_(ULONG) MyTimeBeijing::AddRef()
{
printf("AddRef called.../n");
return ++m_cRef;
}
STDMETHODIMP_(ULONG) MyTimeBeijing::Release()
{
printf("Release called.../n");
ULONG res = --m_cRef; // 用临时变量把修改后的引用计数值缓存起来,
// 因为在对象已经销毁后再引用这个对象的数据将是非法的
if(res == 0)
delete this;
return res;
}
STDMETHODIMP MyTimeBeijing::GetHour(int * hour)
{
time_t ltime;
struct tm *today;
time(<ime);
today = localtime(<ime);
*hour = today->tm_hour;
return S_OK;
}
STDMETHODIMP MyTimeBeijing::GetMinute(int * minute)
{
time_t ltime;
struct tm *today;
time(<ime);
today = localtime(<ime);
*minute = today->tm_min;
return S_OK;
}
STDMETHODIMP MyTimeBeijing::GetSecond(int* second)
{
time_t ltime;
struct tm *today;
time(<ime);
today = localtime(<ime);
*second = today->tm_sec;
return S_OK;
}
3)DLL主文件
//TimeBeijing.cpp
#include "stdafx.h"
#include <objbase.h>
#include <initguid.h>
#include "TimeBeijing.h"
#include "MyTimeBeijing.h"
#include "TimeBeijing_i.c"
HINSTANCE g_hinstDll;//DLL实例句柄
const char * g_RegTable[][3]={//COM对象注册
{"CLSID//{F9A47C8D-A7E4-477b-A27A-47024DE647ED}",
0,
"TimeBeijing"},
{"CLSID//{F9A47C8D-A7E4-477b-A27A-47024DE647ED}//InprocServer32",
0,
(const char * )-1},
{"CLSID//{F9A47C8D-A7E4-477b-A27A-47024DE647ED}//ProgID",0,"Justin.TimeBeijing.1"},
{"Justin.TimeBeijing.1",0,"TimeBeijing"},
{"Justin.TimeBeijing.1//CLSID",0,"{F9A47C8D-A7E4-477b-A27A-47024DE647ED}"},
};
//DLL入口
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
g_hinstDll = (HINSTANCE) hModule;//保存句柄
return TRUE;
}
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv)
{
static MyTimeBeijing *Obj = new MyTimeBeijing;
if(rclsid == CLSID_MyTimeBeijing)
return Obj->QueryInterface(riid, ppv);
return CLASS_E_CLASSNOTAVAILABLE;
}
STDAPI DllCanUnloadNow(void)
{
return E_FAIL;//永远返回E_FAIL
}
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;
}
4)建立TimeBeijing.def文件。该文件是模块定义文件,它可以将DLL中的函数导出。
一个COM服务器必须实现下列这些接口函数:
DllCanUnloadNow 判断服务器是否可以退出
DllGetClassObject 请求获取COM对象
DllRegisterServer 注册COM服务器
DllUnregisterServer 注销COM服务器
所以,TimeBeijing.def文件内容如下:
LIBRARY "TimeBeijing.DLL"
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
5)启动服务器
编译上述文件,应该得到TimeBeijing.DLL。
从命令行中输入:regsvr32 TimeBeijing.DLL 可以启动服务
相反: regsvr32 /u TimeBeijing.DLL 可以停止服务
(五)建立客户程序
客户程序如下所示:
// TEST.cpp
#include "../TimeBeijing.h"//接口定义
#include "../TimeBeijing_i.c"//UUID等的定义
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
//初始化COM库
HRESULT hr = ::CoInitialize(0);
ITimeBeijing * pTimeBeijing = NULL;//声明接口指针
hr = ::CoGetClassObject(CLSID_MyTimeBeijing,//获取对象接口指针
CLSCTX_INPROC,
NULL,IID_ITimeBeijing,
(void **)&pTimeBeijing);
int h, m, s;
if(SUCCEEDED(hr))//使用接口获取时间
{
pTimeBeijing->GetHour(&h);
pTimeBeijing->GetMinute(&m);
pTimeBeijing->GetSecond(&s);
printf("当前时间:%d : %d : %d/n", h, m, s);
}
pTimeBeijing->Release();//释放接口
::CoUninitialize();
::system("pause");
return 0;
}
(六)测试结果
如下:
Constructor called...
QueryInterface called...
AddRef called...
AddRef called...
Release called...
QueryInterface called...
AddRef called...
Release called...
当前时间:19 : 26 : 13
Release called...
Destructor called...
请按任意键继续 . . .
(七)另外的说明
本文较“COM初探(一)”更进了一步,离真正的COM只有一步之遥了。