COM编程入门(二)

 
本文 为刚刚 接触 COM 的程序 指南,解 COM 器内幕以及如何用 C++ 写自己的接口。
上一篇 COM 之后,本文将 讨论 COM 器的内容,解 释编 写自己的 COM 接口和 COM 器所需要的 步骤 和知 ,以及 详细讨论 COM 库对 COM COM 器运行的内部机制。
如果你 读过 上一篇文章。 应该 很熟悉 COM 端是怎 会事了。本文将 讨论 COM 的另一端 ——COM 器。内容包括如何用 C++ 写一个 简单 的不 类库 COM 器。深入到 COM 器的内部 程,毫无遮掩地研究那些 是充分理解 COM 器内部机制的最好方法。
本文假 你精通 C++ 并掌握了上一篇文章所 讨论 的概念和 术语 。在 一部分将包括如下内容:
马观 花看 COM —— 描述 COM 器的基本要求。
器生命其管理 —— 描述 COM 器如何控制加 载时间
实现 接口,从 IUnknown —— 展示如何用 C++ 类编 写一个接口 实现 并描述 IUnknown 之方法的目的。
深入 CoCreateInstance()—— 探究 CoCreateInstance() 用机理。
COM
器的注册 —— 描述完成 器注册所需要的注册表入口。
COM —— 工厂 —— 描述 建客 端要使用的 COM 象的 程。
一个定制接口的例子 —— 例子代 示范了上述概念。
一个使用 器的客 —— 明一个 简单 的客 用程序,用它来 测试 COM 器。
其它内容 —— 源代 调试 的注
马观 花看 COM
本文我 讨论 简单 的一 COM 器, 器( in-process )。 程内 意思是 器被加 到客 端程序的 程空 器都是 DLLs ,并且与客 端程序同在一台 算机上。
器在被 COM 使用之前必 须满 足两个条件或 准:
1
、 必 正确在注册表的 HKEY_CLASSES_ROOT/CLSID 键值 下注册。
2
、 必 须输 DllGetClassObject() 函数。
器运行的最小需求。在注册表的 HKEY_CLASSES_ROOT/CLSID 键值 下必 须创 建一个 键值 器的 GUID 为键 名字, 键值 包含两个 键值 ,一 器的位置,而是 器的 线 程模型。 COM 库对 DllGetClassObject() 函数 用是在 CoCreateInstance() API 中完成的。
有三个函数通常也要 出:
o DllCanUnloadNow():
COM 库调 用来 检查 器被从内存中卸
o DllRegisterServer():
RegSvr32 的安装 用程序 用来注 器。
o DllUnregisterServer():
由卸 载实 用程序 用来 除由 DllRegisterServer() 建的注册表入口。
另外,只 出正确的函数是不 —— 遵循 COM 范, 这样 COM 和客 端程序才能使 器。
器生命其管理
DLL
器的一个与众不同的方面是控制它 被加 时间 准的 ”DLLs 的并且是在 用程序使用它 们时 被随机加 / 或卸 DLL 器也是被 的,因 不管怎 们毕 DLL ,但 COM 提供了一 机制,它允 器命令 COM 它。 是通 过输 出函数 DllCanUnloadNow() 实现 的。 个函数的原型如下:
     HRESULT DllCanUnloadNow();

当客 户应 用程序 COM API CoFreeUnusedLibraries() ,通常出于其空 闲处 理期 COM 历这 个客 用已加 所有的 DLL 器并通 过调 用它的 DllCanUnloadNow() 函数 查询每 器。另一方面,如果某个 器确定它不再需要 留内存,它可以返回 S_OK COM 将它卸
器通 过简单 的引用 数来确定它是否能被卸 。下面是 DllCanUnloadNow() 实现
extern UINT g_uDllRefCount; //
器的引用
HRESULT DllCanUnloadNow()
{
  return (g_uDllRefCount > 0) ? S_FALSE : S_OK;
}

如何 理引用 数将在下一 节涉 及到具体代 码时讨论
实现 接口,从 IUnknown
有必要回想一下 IUnknown 派生的 一个接口。因 IUnknown 包含了两个 COM 象的基本特性 —— 引用 数和接口 查询 。当你 类时 coclass ), 要写一个 足自己需要的 IUnknown 实现 。以 实现 IUnknown 接口的 类为 —— 下面 个例子可能是你 写的最 简单 的一个 。我 将在一个叫做 CUnknownImpl C++ 实现 IUnknown 。下面是 的声明:
class CUnknownImpl : public IUnknown
{
public:
  //
构造函数和析构器
  CUnknownImpl();
  virtual ~CUnknownImpl();

  // IUnknown
方法
  ULONG AddRef();
  ULONG Release)();
  HRESULT QueryInterface( REFIID riid, void** ppv );

protected:
  UINT m_uRefCount; //
象的引用
};

构造器和析构器
构造器和析构器管理 器的引用 数:
CUnknownImpl::CUnknownImpl()
{
  m_uRefCount = 0;
  g_uDllRefCount++;
}

CUnknownImpl::~CUnknownImpl()
{
  g_uDllRefCount--;
}

建新的 COM ,构造器被 用,它增 器的引用 数以保持 留内存。同 象的引用 数初始化 零。当 COM 象被摧 毁时 ,它减 器的引用 数。
AddRef()
Release()

两个方法控制 COM 象的生命期。 AddRef() 简单
ULONG CUnknownImpl::AddRef()
{
  return ++m_uRefCount;
}

AddRef()
只增加 象的引用 数并返回更新的 数。
Release()
简单
ULONG CUnknownImpl::Release()
{
ULONG uRet = --m_uRefCount;

  if ( 0 == m_uRefCount ) //
是否 放了最后的引用 ?
    delete this;

  return uRet;
}

除了减少 象的引用 数外,如果没有另外的明确引用, Release() 将摧 毁对 象。 Release() 也返回更新的引用 数。注意 Release() 实现 COM 象在堆中 建。如果你在全局粘上 建某个 象,当 试图删 除自己 就会出 问题
应该 明白了 在客 用程序中正确 AddRef() Release() 是如此重要!如果在 了做得不 ,你使用的 象会被很快摧 这样 在整 器中内存会很快溢出 用程序下次存 器代 码时
如果你 写多 线 用,可能会想到使用 ++& 替代 InterlockedIncrement() InterlockedDecrement() 线 程安全 问题 ++&—— 用于 单线 器很保 ,因 即使客 用是多 线 程的并从不同的 线 程中 行方法 用, COM 都会按 器的方法 用。也就是 ,一旦一个方法 始,所有其它 试图调 用方法的 线 程都将阻塞,直到第一个方法返回。 COM 本身确 器一次不会被一个以上的 线 入。

QueryInterface()

QueryInterface()
QI() ,由客 端程序 个函数从 COM 求不同的接口。我 在例子代 中因 实现 一个接口, QI() 会很容易使用。 QI() 有两个参数:一个是所 求的接口 IID ,一个是指 冲大小,如果 查询 成功, QI() 将接口指 地址存 冲指 中。
HRESULT CUnknownImpl::QueryInterface ( REFIID riid, void** ppv )
{
HRESULT hrRet = S_OK;

  //
QI() 初始化 *ppv NULL.
  *ppv = NULL;

  //
如果客 求提供的接口, *ppv. 赋值
  if ( IsEqualIID ( riid, IID_IUnknown ))
    {
    *ppv = (IUnknown*) this;
    }
  else
    {
    //
不提供客 求的接口
    hrRet = E_NOINTERFACE;
    }

  //
如果返回一个接口指 AddRef() 增加引用 .
  if ( S_OK == hrRet )
    {
    ((IUnknown*) *ppv)->AddRef();
    }

  return hrRet;
}
QI() 中做了三件不同的事情:
1
、初始化 入的指 针为 NULL[*ppv = NULL;]
2
检查 riid ,确定 coclass 实现 了客 端所 求接口 .
[if ( IsEqualIID ( riid, IID_IUnknown ))]
3
、如果确 实实现 勒索 求的接口, 增加 COM 象的引用 数。
[((IUnknown*) *ppv)->AddRef();]

AddRef()
用很 关键
  *ppv = (IUnknown*) this;

建新的 COM 象引用,就必 须调 个函数通知 COM 个新引用成立。在 AddRef() 用中的 转换 IUnknown* 看起来好像多余,但是在 QI() 中初始化的 *ppv 有可能不是 IUnknown* 型,所以最好是 习惯对 转换 。。
上面我 经讨论 了一些 DLL 器的内部 细节 ,接下来 看一看当客 CoCreateInstance() 是如何 器的。

深入 CoCreateInstance()

在本文的第一部分中,我 们见过 CoCreateInstance()API ,其作用是当客 ,用它来 象。从客 端的立 看,它是一个黑盒子。只要用正确的参数 用它即可得到一个 COM 象。它并没有什 魔法,只是在一个定 良好的 程中加 COM 器, 求的 COM 象并返回所要的指 。就 些。
下面 浏览 一下 程。 里要 及到几个不太熟悉的 术语 ,但不用着急,后面会 详细讨论
1
、客 端程序 CoCreateInstance() 传递组 CLSID 以及所要接口的 IID
2
COM HKEY_CLASSES_ROOT/CLSID. 键值 器的 CLSID 键值 键值 器的注册信息。
3
COM 库读 DLL 的全路径并将 DLL 到客 端的 程空
4
COM 库调 器中 DllGetClassObject() 函数 求的 类请 工厂。
5
建一个 工厂并将它从 DllGetClassObject() 返回。
6
COM 工厂中 CreateInstance() 方法 建客 端程序 求的 COM 象。
7
CreateInstance() 返回一个接口指 到客 端程序。

COM
器注册
 
COM 器必 Windows 注册表中正确注册以后才能正常工作。如果你看一下注册表中的 HKEY_CLASSES_ROOT/CLSID ,就会 发现 大把大把子 ,它 就是在 算机上注册的 COM 器。当某个 COM 器注册后(通常是用 DllRegisterServer() 行注册),就会以 准的注册表格式在 CLSID 建一个 ,它名字 器的 GUID 。下面是一个 这样 的例子:
 
{067DF822-EAB6-11cf-B56E-00A0244D5087}
 
 
大括弧和 字符是必不可少的,字母大小写均可。
的默 认值 是人可 值别 名,使用 VC OLE/COM 浏览 器可以察看到它
GUID 的子 可以存 其它信息。需要 建什 COM 器的 型以及 COM 器的使用方法。 于本文例子中 简单 程内服 器,我 们值 需要一个子 InProcServer32
InProcServer32 包含两个串: 两个串的缺省 是服 DLL 的全路径和 线 程模型 ThreadingModel )。 线 程模型超出了本文所 及的范 ,我 先接受 个概念, 里我 指的是 单线 程服 器,用的模式 Apartment (即 单线 程公寓)。
 
COM —— 工厂
 
回首看一看客 端的 COM ,它是如何以自己独立于 言的方式 建和 销毁 COM 象。客 CoCreateInstance() 建新的 COM 象。 在我 来看看它在服 器端是如何工作的。
实现组 候,都要写一个旁 类负责创 建第一个 例。 个旁 就叫 工厂( class factory ),其唯一目的是 COM 象。之所以要一个 工厂,是因 为语 言无 故。 COM 本身并不 象,因 它不是独立于 言的也不是独立于 实现 的。
当某个客 端想要 建一个 COM COM 就从 COM 工厂。然后 工厂 COM 象并将它返回客 端。它 的通 机制由函数 DllGetClassObject() 来提供。
术语 工厂 类对 实际 上是一回事。没有那个 单词 能精确描述 工厂的作用和 ,但正是 个工厂 建了 COM 象,而不是 COM 。将 工厂 理解成 象工厂 可能会更有助于理解( 实际 MFC 就是 这样 理解的 —— 它的 工厂 实现 就叫做 COleObjectFactory )。但 工厂 是正式 术语 ,所以本文也 这样 用。
COM 库调 DllGetClassObject() ,它 传递 求的 CLSID 。服 负责为 求的 CLSID 建者各 工厂并将它返回。 工厂本身就是一个 ,并且 实现 IClassFactory 接口。如果 DllGetClassObject() 用成功,它返回一个 IClassFactory 针给 COM ,然后 COM IClassFactory 接口方法 建客 端所 求的 COM 例。
一下是IClassFactory接口:
 
struct IClassFactory : public IUnknown
{
 HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppvObject );
  HRESULT LockServer( BOOL fLock );
};
 
其中, CreateInstance() COM 象的方法。 LockServer() 在必要 时让 COM 增加或减少服 器的引用 数。
 
一个定制接口的例子
个工程是一个能运行的 DLL 器例子, 象由 工厂 建,此 DLL 器在 CSimpleMsgBoxImpl 实现 了一个接口: ISimpleMsgBox
 
接口定
 
的新接口是 ISimpleMsgBox 。所有的接口多必 IUnknown 派生。 个接口只有一个方法 DoSimpleMsgBox() 。注意它返回 HRESULT 。所有的方法都 应该 返回 HRESULT 型,并且所有返回到 用者的其它数据都 应该 参数操作。
struct ISimpleMsgBox : public IUnknown
{
 // IUnknown 方法
 ULONG AddRef();
 ULONG Release();
 HRESULT QueryInterface( REFIID riid, void** ppv );
 
 // ISimpleMsgBox 方法
 HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );
};
 
struct __declspec(uuid("{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}")) ISimpleMsgBox;
 
__declspec 的一行将一个 GUID 赋值给 ISimpleMsgBox ,并且以后可以用 __uuidof 操作符来 GUID 两个 西都是微 C++ 展。
DoSimpleMsgBox() 的第二个参数是 BSTR 型。意思是二 制串 —— 即定 序列位的 COM 表示。 BSTRs 主要用于 Visual Basic Windows Scripting Host 的脚本客 端。
 
接下来 个接口由 CSimpleMsgBoxImpl C++ 实现 。其定 如下:
class CSimpleMsgBoxImpl : public ISimpleMsgBox 
{
public:
 CSimpleMsgBoxImpl();
 virtual ~CSimpleMsgBoxImpl();
 
 // IUnknown 方法
 ULONG AddRef();
 ULONG Release();
 HRESULT QueryInterface( REFIID riid, void** ppv );
 
 // ISimpleMsgBox 方法
 HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );
 
protected:
 ULONG m_uRefCount;
};
 
class __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;
 
当某一客 端想要 建一个 SimpleMsgBox COM ,它 应该 用下面 这样 的代
 
ISimpleMsgBox* pIMsgBox;
HRESULT hr;
 
// CLSID
hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),              NULL,             // 非聚合
    CLSCTX_INPROC_SERVER, // 程内服
   __uuidof(ISimpleMsgBox), // 求接口的 IID
 (void**) &pIMsgBox );     // 返回的接口指 的地址
 
工厂 实现
 
工厂 SimpleMsgBox 是在一个叫做 CSimpleMsgBoxClassFactory C++ 实现 的:
class CSimpleMsgBoxClassFactory : public IClassFactory
{
public:
 CSimpleMsgBoxClassFactory();
 virtual ~CSimpleMsgBoxClassFactory();
 
 // IUnknown 方法
 ULONG AddRef();
 ULONG Release();
 HRESULT QueryInterface( REFIID riid, void** ppv );
 
 // IClassFactory 方法
 HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppv );
 HRESULT LockServer( BOOL fLock );
 
protected:
  ULONG m_uRefCount;
};
 
构造函数、析构函数和 IUnknown 方法都和前面例子中的一 ,不同的只有 IClassFactory 的方法, LockServer() ,看起来相当更 简单
 
HRESULT CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock )
{
 fLock ? g_uDllLockCount++ : g_uDllLockCount--;
  return S_OK;
}
 
CreateInstance() 是重点。我 们说过这 个方法 负责创 建新的 CSimpleMsgBoxImpl 象。 们进 一下它的原型和参数:
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                          REFIID riid,
                          void** ppv );
 
第一个参数 pUnkOuter 只用于聚合的新 象,指向 外部的 ”COM 象,也就是 外部 象将包含此新 象。 象的聚合超出了   
本文的 讨论 ,本文的例子 象也不支持聚合。
riid ppv 与在 QueryInterface() 中的用法一 —— 是客 端所 求的接口 IID 和存 接口指 的指 针缓 冲。
下面是 CreateInstance() 实现 。它从参数的有效性 检查 和参数的初始化 始。
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                          REFIID riid,
                          void** ppv )
{
 // 不支持聚合,所以 个参数 pUnkOuter 须为 NULL.
  if ( NULL != pUnkOuter )
    return CLASS_E_NOAGGREGATION;
 
  // 检查 ppv 是不是 void*
  if ( IsBadWritePtr ( ppv, sizeof(void*) ))
    return E_POINTER;
 
 *ppv = NULL;
 
检查 完参数的有效性后,就可以 建一个新的 象了。
CSimpleMsgBoxImpl* pMsgbox;
 
 // 建一个新的 COM
  pMsgbox = new CSimpleMsgBoxImpl;
 
 if ( NULL == pMsgbox )
    return E_OUTOFMEMORY;
 
最后,用 QI() 查询 端所 求的新 象的接口。如果 QI() 则这 象不可用,必 须删 除它。
 
HRESULT hrRet;
 
 // QI 查询 端所 求的 象接口
  hrRet = pMsgbox->QueryInterface ( riid, ppv );
 
  // 如果 QI 则删 COM 象,因 端不能使用它(客 端没有
 // 象的任何接口)
 if ( FAILED(hrRet) )
    delete pMsgbox;
 
 return hrRet;
}
 
深入DllGetClassObject()
深入 DllGetClassObject() 内部。它的原型是:
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv );
rclsid 是客 端所 求的 CLSID 个函数必 返回指定 工厂。
里的两个参数: riid ppv QI() 的参数。不 个函数中, riid 指的是 COM 求的 工厂 接口的 IID 。通常就是 IID_IClassFactory
DllGetClassObject() 建一个新的 COM 象( 工厂),所以代 IClassFactory::CreateInstance() 十分相似。 始也是 行一些有效性 检查 以及初始化。
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv )
{
 // 检查 端所要的 CSimpleMsgBoxImpl 工厂
  if ( !InlineIsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) ))
    return CLASS_E_CLASSNOTAVAILABLE;
 
 // 检查 ppv 是不是 void*
  if ( IsBadWritePtr ( ppv, sizeof(void*) ))
    return E_POINTER;
 
 *ppv = NULL;
 
第一个 if 检查 rclsid 参数。我 的服 器只有一个 ,所以 rclsid CSimpleMsgBoxImpl CLSID __uuidof 操作符 取先前在 __declspec(uuid()) 声明中指定的 CsimpleMsgBoxImpl GUID
下一 建一个 工厂 象。
CSimpleMsgBoxClassFactory* pFactory;
 
 // 构造一个新的 工厂
  pFactory = new CSimpleMsgBoxClassFactory;
 
 if ( NULL == pFactory )
    return E_OUTOFMEMORY;
 
里的 理与 CreateInstance() 中所做的有所不同。在 CreateInstance() 中是 用了 QI() ,并且如果 用失 则删 COM 象。
可以把自己假 成一个所 建的 COM 象的客 端, AddRef() 行一次引用 数( COUNT = 1 )。然后 QI() 。如果 QI() 用成功,它将再一次用 AddRef() 行引用 数( COUNT = 2 )。如果 QI() 用失 。引用 数将保持 原来的 COUNT = 1 )。
QI() 用之后, 工厂 象就使用完了,因此要 Release() 放它。如 QI() 用失 象将自我 除(因 引用 数将 零),所以最 终结 果是一 的。
// AddRef() 增加一个 工厂引用 数,因 正在使用它
pFactory->AddRef();
 
HRESULT hrRet;
 
 // QI() 查询 端所要的 工厂接口
  hrRet = pFactory->QueryInterface ( riid, ppv );
  
  // 使用完 工厂后 Release() 放它
 pFactory->Release();
 
 return hrRet;
}
 
QueryInterface()
 
前面 讨论过 QI() 实现 ,但 是有必要再看一看 工厂的 QI() ,因 它是一个很 现实 的例子,其中 COM 实现 的不光是 IUnknown 。首先 行的是 ppv 冲的有效性 检查 以及初始化。
HRESULT CSimpleMsgBoxClassFactory::QueryInterface( REFIID riid, void** ppv )
{
HRESULT hrRet = S_OK;
 
 // 检查 ppv 是不是 void*
  if ( IsBadWritePtr ( ppv, sizeof(void*) ))
    return E_POINTER;
 
 // 准的 QI 初始化,将 赋值为 NULL.
 *ppv = NULL;
 
接下来 检查 riid ,看看它是不是 工厂 实现 的接口之一: IUnknown IclassFactory
 // 如果客 求一个有效接口, 扶植 *ppv.
  if ( InlineIsEqualGUID ( riid, IID_IUnknown ))
    {
    *ppv = (IUnknown*) this;
    }
 else if ( InlineIsEqualGUID ( riid, IID_IClassFactory ))
    {
    *ppv = (IClassFactory*) this;
    }
 else
    {
    hrRet = E_NOINTERFACE;
    }
 
最后,如果 riid 是有效接口, 则调 用接口的 AddRef() ,然后返回。
 // 如果返回有效接口指 则调 AddRef()
  if ( S_OK == hrRet )
    {
    ((IUnknown*) *ppv)->AddRef();
    }
 
 return hrRet;
}
 
ISimpleMsgBox 实现
 
最后的也是必不可少的一 ISimpleMsgBox 实现 ,我 的代 实现 ISimpleMsgBox 的方法 DoSimpleMsgBox() 。首先用微 _bstr_t bsMessageText 转换 TCHAR 串。
HRESULT CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )
{
_bstr_t bsMsg = bsMessageText;
LPCTSTR szMsg = (TCHAR*) bsMsg; // 如果需要的 ,用 _bstr_t 将串 转换为 ANSI
做完 转换 的工作后, 示信息框,然后返回。
  MessageBox ( hwndParent, szMsg, _T("Simple Message Box"), MB_OK );
  return S_OK;
}
 
使用服 器的客
 
完成了一个超 棒的 COM 器,如何使用它呢 ? 的接口一个定制接口,也就是 它只能被 C C++ 端使用。(如果在 中同 时实现 IDispatch 接口,那我 几乎就可以在任何客 境中 ——Visual Basic Windows Scripting Host Web 面, PerlScript 等使用 COM 象。有 关这 方面的内容我 留待另外的文章 讨论 )。本文提供了一个使用 ISimpleMsgBox 的例子程序。 个程序基于用 Win32 用程序向 建立的 Hello World 例子。文件菜 包含两个 测试 器的命令:
所示:
 
Test MsgBox COM Server 命令 CSimpleMsgBoxImpl 象并 DoSimpleMsgBox() 。因 为这
是个 简单 的方法,要写的代
 
先用 CoCreateInstance() 建一个 COM 象。
 
void DoMsgBoxTest(HWND hMainWnd)
{
ISimpleMsgBox* pIMsgBox;
HRESULT hr;
 
hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl), // CLSID
              NULL,        // 非聚合
              CLSCTX_INPROC_SERVER, // 只使用 程内服
              __uuidof(ISimpleMsgBox), // 求接口的 IID
              (void**) &pIMsgBox ); // 接口指
 
 if ( FAILED(hr) )
    return;
 
然后 DoSimpleMsgBox() 方法并 放接口。
  pIMsgBox->DoSimpleMsgBox ( hMainWnd, _bstr_t("Hello COM!") );
 pIMsgBox->Release();
}
 
这么简单 。代 中从 到尾都有 TRACE 句, 这样 调试 器中运行 测试 程序就可以看到服 器的 一个方法
是如何被 用的。
另外一个菜 命令是 CoFreeUnusedLibraries() 函数,从中你能看到服 DllCanUnloadNow() 函数的运行。
其它 细节 -COM
COM 中有些宏 藏了 实现细节 ,并允 C C++ 端使用相同的声明。本文中没有使用宏,但在例子代
中用到了 些宏,所以必 掌握它 的用法。下面是 ISimpleMsgBox 的声明
struct ISimpleMsgBox : public IUnknown
{
 // IUnknown 方法
 STDMETHOD_(ULONG, AddRef)() PURE;
 STDMETHOD_(ULONG, Release)() PURE;
 STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE;
 
 // ISimpleMsgBox 方法
 STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText) PURE;
};
STDMETHOD() 包含 virtual 关键 字,返回 型和 范。 STDMETHOD_() 也一 ,除非你指定不
同的返回 型。 PURE 展了 C++ “=0” ,使此函数成 一个 函数。
STDMETHOD() STDMETHOD_() 对应 的宏用于方法 实现 ——STDMETHODIMP STDMETHODIMP_()
例如 DoSimpleMsgBox() 实现
STDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )
{
 ...
}
最后, 准的 出函数用 STDAPI 宏声明,如:
STDAPI DllRegisterServer()
STDAPI 包括返回 型和 范。要注意 STDAPI 不能和 __declspec(dllexport) 一起使用,
STDAPI 展。 出必 使用 .DEF 文件。
 
器注册以及反注册
前面 讲过 实现 DllRegisterServer() DllUnregisterServer() 两个函数。它 的工作是 建和
COM 器的注册表入口。其代 都是 注册表的 理,所以在此不必 言,只是列出 DllRegisterServer() 建的注册表入口:
键值
 
HKEY_CLASSES_ROOT
 
 
CLSID
 
 
{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}
Default="SimpleMsgBox class"
 
InProcServer32
Default=[path to DLL]; ThreadingModel="Apartment"
 
 
于例子代 的注
本文的例子代 在一个 WORKSPACE (工作 )文件中( SimpleComSvr.dsw )同 包含了服 器的源代 测试
器所用的客 端源代 。在 VC IDE 境中可以同 们进 理。在工作 的同 级层 次有两个工程都要
用到的 文件,但 个工程都有自己的子目
的公共 文件是:
ISimpleMsgBox.h—— ISimpleMsgBox 文件。
SimpleMsgBoxComDef.h—— 包含 __declspec(uuid()) 的声明。 些声明都在 独的文件中,因
端需要 CSimpleMsgBoxImpl GUID ,不是它的定 。将 GUID 移到 独的文件中,使客 端在存取 GUID 不依
CSimpleMsgBoxImpl 的内部 。它是接口, ISimpleMsgBox 端很重要。
正如前面所 的,必 .DEF 文件来从服 出四个 准的 出函数。下面是例子工程的 .DEF 文件:
EXPORTS
 DllRegisterServer PRIVATE
  DllUnregisterServer PRIVATE
 DllGetClassObject PRIVATE
 DllCanUnloadNow   PRIVATE
 
一行都包含函数名和 PRIVATE 关键 字。 关键 字的意思是:此函数是 出函数,但不包含在 import lib )中。也就是 端不能直接从代 个函数,即使是 接了 也不行。 关键 要用的,否 则链 接器会出
 
在服 器中 置断点
如果你想在服 器代 置断点,有两 方法:第一 是将服 器工程 (MsgBoxSvr) 工程,然后 调试 MSVC 调试 要运行的可 行程序。 入客 测试 程序的全路径,你必 事先建立好。第二 方法是将客 端工程 (TestClient) 工程,配置工程的从属( dependencies )属性,以便服 器工程从属于客 端工程。 这样 如果你改 了服 器的代 ,那 编译 端工程 会自 重新 编译 器工程代 。最后 要做的是当你 调试 MSVC 器符号( symbols )。
下面是 置工程属性的 对话 框: Project->Dependencies
 
了加 器符号,打 TestClient 的工程 置( Project->Settings ), 选择 Debug 标签 ,并在 Category 合框中 选择 Additional DLLs 。在列表框中 单击 New 一个入口,然后 入服 DLL 的全路径名。 如下 所示:
 
这样设 置以后,根据 实际 源代 的所在位置, DLL 的路径将会做自 动调
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值