COM初探(二)

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(&ltime);
 today = localtime(&ltime);

 *hour = today->tm_hour;
 return S_OK;
}

STDMETHODIMP MyTimeBeijing::GetMinute(int * minute)
{
 time_t ltime;
 struct tm *today;
 
 time(&ltime);
 today = localtime(&ltime);
 
 *minute = today->tm_min;
 
 return S_OK;
}

STDMETHODIMP MyTimeBeijing::GetSecond(int* second)
{
 time_t ltime;
 struct tm *today;
 
 time(&ltime);
 today = localtime(&ltime);
 
 *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只有一步之遥了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值