65.9K
CodeProject 正在变化。 阅读更多。
Home

编写 Shell 扩展的完整入门指南 - 第三部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (21投票s)

2000 年 3 月 30 日

viewsIcon

569998

downloadIcon

4141

一个关于编写 Shell 扩展的教程,该扩展可以显示文件的弹出信息。

目录

引言

在指南的第一部分第二部分中,我展示了如何编写上下文菜单扩展。在第三部分中,我将演示一种不同的扩展类型,解释如何与 Shell 共享内存,并展示如何将 MFC 与 ATL 一起使用。本部分假设您已从前两部分理解了 Shell 扩展的基础知识,并熟悉 MFC。

Active Desktop Shell 引入了一项新功能:当鼠标悬停在某些对象上时,会显示一个描述性的工具提示。例如,将鼠标悬停在我的文档上会显示此工具提示

 [My Computer tooltip - 6K]

其他对象,如“我的电脑”和“控制面板”也有类似的工具提示。这些文本被称为信息提示 (infotips),因为它们是提供有关鼠标悬停在其上的文件、文件夹或对象的信息的工具提示。我们可以为 Shell 中的其他对象提供自己的信息提示文本,使用信息提示扩展。一个您可能已经见过的例子是 WinZip,它会显示压缩文件的内容

 [WinZip tooltip - 5K]

本文的示例扩展将是一个快速文本文件查看器 - 它将显示文件的第一行以及文件的总大小。当用户将鼠标悬停在 .TXT 文件上时,此信息将显示在信息提示中。

请记住,VC 7(可能还有 VC 8)用户在编译前需要更改一些设置。请参阅 第一部分的 README 部分 以了解详细信息。

使用 AppWizard 开始

像在之前的文章中一样,运行 AppWizard 并创建一个新的 ATL COM 应用程序。我们将它命名为 *TxtInfo*。由于这次我们要使用 MFC,请勾选 *支持 MFC* 复选框,然后单击 *完成*。

要向 DLL 添加 COM 对象,请转到 ClassView 树,右键单击 *TxtInfo classes* 项,然后选择 *新建 ATL 对象*。(在 VC 7 中,右键单击该项并选择 *添加|添加类*。)像以前一样,在向导中选择“简单对象”,并将对象命名为 *TxtInfoShlExt*。这将创建一个 C++ 类 CTxtInfoShlExt,它将实现该扩展。

如果您查看 ClassView 树,您会看到一个 CTxtInfoApp 类,它继承自 CWinApp。这个类的存在以及全局变量 theApp,使得 MFC 可供我们使用,就像我们编写一个普通的 MFC DLL 而没有任何 ATL 一样。

初始化接口

在之前文章的上下文菜单扩展中,我们实现了 IShellExtInit 接口,这是 Explorer 初始化我们对象的方式。还有另一个用于某些 Shell 扩展的初始化接口,IPersistFile,这是信息提示扩展使用的接口。为什么会有这种区别?如果您还记得,IShellExtInit::Initialize() 接收一个 IDataObject 指针,它可以使用该指针枚举所有选定的文件。只能操作单个文件的扩展使用 IPersistFile。由于鼠标一次不能悬停在多个对象上,信息提示扩展一次只能处理一个文件,因此它使用 IPersistFile

我们首先需要将 IPersistFile 添加到 CTxtInfoShlExt 实现的接口列表中。打开 *TxtInfoShlExt.h*,并添加粗体显示的行。

#include <comdef.h>
#include <shlobj.h>
 
class CTxtInfoShlExt :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>,
  public IPersistFile
{
  BEGIN_COM_MAP(CTxtInfoShlExt)
	COM_INTERFACE_ENTRY(IPersistFile)
  END_COM_MAP()

我们还需要一个变量来存储 Explorer 在初始化期间提供给我们的文件名。

protected:
  CString m_sFilename;

请注意,我们可以在任何需要的地方使用 MFC 对象。

如果您查阅 IPersistFile 的文档,您会看到大量方法。幸运的是,对于 Shell 扩展而言,我们只需要实现 Load() 方法,而忽略其他方法。这是 IPersistFile 方法的原型。

class CTxtInfoShlExt : ...
{
  // ...
 
public:
  STDMETHODIMP GetClassID(LPCLSID)      { return E_NOTIMPL; }
  STDMETHODIMP IsDirty()                { return E_NOTIMPL; }
  STDMETHODIMP Load(LPCOLESTR, DWORD);
  STDMETHODIMP Save(LPCOLESTR, BOOL)    { return E_NOTIMPL; }
  STDMETHODIMP SaveCompleted(LPCOLESTR) { return E_NOTIMPL; }
  STDMETHODIMP GetCurFile(LPOLESTR*)    { return E_NOTIMPL; }
};

除了 Load() 之外的所有方法都返回 E_NOTIMPL,表示我们没有实现它们。为了使情况更好,我们的 Load() 实现也非常简单。我们只是存储 Explorer 传递给我们的文件名。这是鼠标悬停的文件名的完整路径。

HRESULT CTxtInfoShlExt::Load (
  LPCOLESTR wszFilename, DWORD dwMode )
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());  // init MFC
 
  // Let CString convert the filename to ANSI.
  m_sFilename = wszFilename;
 
  return S_OK;
}

函数中的第一行至关重要。AFX_MANAGE_STATE 宏对于 MFC 的正常工作是必需的。因为我们的 DLL 是由一个非 MFC 应用程序加载的,所以每个使用 MFC 的导出函数都必须手动初始化 MFC。如果您不包含这一行,许多 MFC 函数(主要是与资源相关的函数)将无法正常工作或出现断言失败。

文件名存储在 m_sFilename 中,供以后使用。请注意,我利用了 CString 的赋值运算符将字符串转换为 ANSI,前提是 DLL 是以 ANSI 构建的。

创建工具提示文本

在 Explorer 调用我们的 Load() 方法后,它会调用 QueryInterface() 来获取另一个接口:IQueryInfoIQueryInfo 是一个相当简单的接口,只有两个方法(实际上只使用一个)。再次打开 *TxtInfoShlExt.h*,并添加此处列出的粗体显示的行。

class CTxtInfoShlExt : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>,
  public IPersistFile,
  public IQueryInfo
{
  BEGIN_COM_MAP(CTxtInfoShlExt)
	COM_INTERFACE_ENTRY(IPersistFile)
	COM_INTERFACE_ENTRY(IQueryInfo)
  END_COM_MAP()

然后添加 IQueryInfo 方法的原型。

class CTxtInfoShlExt : ...
{
  // ...
 
  STDMETHODIMP GetInfoFlags(DWORD*)     { return E_NOTIMPL; }
  STDMETHODIMP GetInfoTip(DWORD, LPWSTR*);
};

GetInfoFlags() 方法不使用,所以我们只需要返回 E_NOTIMPLGetInfoTip() 是我们向 Explorer 返回文本以在工具提示中显示的地方。首先,是开头那些枯燥的部分。

HRESULT CTxtInfoShlExt::GetInfoTip (
  DWORD dwFlags, LPWSTR* ppwszTip )
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());  // init MFC
 
USES_CONVERSION;
LPMALLOC   pMalloc;
CStdioFile file;
DWORD      dwFileSize;
CString    sFirstLine;
BOOL       bReadLine;
CString    sTooltip;

同样,AFX_MANAGE_STATE 调用是第一个,用于初始化 MFC。这必须在函数的开头完成,甚至在变量声明之前,因为局部变量的构造函数可能会调用 MFC 函数。

dwFlags 目前不使用。ppwszTip 是一个指向 LPWSTR 的指针,我们将将其设置为指向我们必须分配的一个缓冲区。

首先,我们将尝试以只读方式打开文件。我们知道文件名,因为它是在之前的 Load() 函数中存储的。

  if ( !file.Open(m_sFilename, CFile::modeRead|CFile::shareDenyWrite) )
    return E_FAIL;

现在,由于我们需要使用 Shell 的内存分配器来分配缓冲区,因此我们需要一个 IMalloc 接口指针。我们通过调用 SHGetMalloc() 函数来获得它。

  if ( FAILED(SHGetMalloc(&pMalloc)) )
    return E_FAIL;

稍后我将详细介绍 IMalloc。下一步是获取文件大小,并读取第一行。

  // Get the size of the file.
  dwFileSize = file.GetLength();
 
  // Read in the first line from the file.
  bReadLine = file.ReadString ( sFirstLine );

bReadLine 通常会是 TRUE,除非文件无法访问或长度为 0 字节。下一步是创建工具提示的第一部分,其中列出了文件大小。

  sTooltip.Format ( _T("File size: %lu"), dwFileSize );

现在,如果我们可以读取文件的第一行,我们就将其添加到工具提示中。

  if ( bReadLine )
    {
    sTooltip += _T("\n");
    sTooltip += sFirstLine;
    }

现在我们有了完整的工具提示,我们需要分配一个缓冲区。这就是我们使用 IMalloc 的地方。SHGetMalloc() 返回的指针是 Shell 的 IMalloc 接口的副本。我们用该接口分配的任何内存都驻留在 Shell 的进程空间中,因此 Shell 可以使用它。更重要的是,Shell 也可以释放它。所以我们所做的是分配缓冲区,然后就忘记它。Shell 在使用完内存后会将其释放。

还需要注意的一点是,我们返回给 Shell 的字符串必须是 Unicode 格式。这就是为什么下面的 Alloc() 调用中的计算乘以 sizeof(wchar_t);仅仅为 lstrlen(sToolTip) 分配内存只会分配所需内存量的一半。

  *ppwszTip = (LPWSTR) pMalloc->Alloc ( (1 + lstrlen(sTooltip)) * sizeof(wchar_t) );
 
  if ( NULL == *ppwszTip )
    {
    pMalloc->Release();
    return E_OUTOFMEMORY;
    }
 
  // Use the Unicode string copy function to put the tooltip text in the buffer.
  wcscpy ( *ppwszTip, T2COLE(LPCTSTR(sTooltip)) );

最后一步是释放我们之前获得的 IMalloc 接口。

  pMalloc->Release();
  return S_OK;
}

就这样!Explorer 获取 *ppwszTip 中的字符串并在工具提示中显示它。

 [text file tooltip - 4K]

注册外壳扩展

信息提示扩展的注册方式与上下文菜单扩展略有不同。我们的扩展程序在 HKEY_CLASSES_ROOT 的一个子项下注册,该子项的名称是我们想要处理的文件扩展名。在这种情况下,它是 HKCR\.txt。但等等,还有更奇怪的!您可能会认为 ShellEx 子项的名称应该是“TooltipHandlers”这样更具逻辑性的名称。不对!该键的名称是 "{00021500-0000-0000-C000-000000000046}"。

我认为 Microsoft 正在试图偷偷地将一些 Shell 扩展隐藏起来!如果您在注册表中进行探索,您会发现其他名为 GUID 的 ShellEx 子项。上面的 GUID 恰好是 IQueryInfo 的 GUID。

这是告诉 Explorer 在 .TXT 文件上调用我们扩展的 RGS 脚本。

HKCR
{
  NoRemove .txt
  {
    NoRemove shellex
    {
      {00021500-0000-0000-C000-000000000046} = s '{F4D78AE1-05AB-11D4-8D3B-444553540000}'
    }
  }
}

您可以通过复制上述代码片段,并将“.txt”替换为您想要的任何扩展名,轻松地使该扩展程序对其他扩展名生效。不幸的是,您不能在 *AllFileSystemObjects 键下注册信息提示扩展程序以使其对所有文件生效。

与我们之前的扩展一样,在基于 NT 的操作系统上,我们需要将我们的扩展添加到系统的批准扩展列表中。执行此操作的代码位于示例项目中的 DllRegisterServer()DllUnregisterServer() 函数中。

待续...

在下一部分(第四部分)中,我们将回到上下文菜单的世界,并了解一种新的扩展类型,即拖放处理程序。我们还将看到更多 MFC 的用法。

版权和许可

本文受版权保护,©2000-2006 Michael Dunn。我知道这不会阻止人们在网上到处复制它,但我还是必须说出来。如果您有兴趣翻译本文,请给我发电子邮件告知我。我不认为我会拒绝任何人进行翻译的许可,只是希望知道有翻译的存在,以便我在此处发布链接。

伴随本文的演示代码已发布到公共领域。我这样发布是为了让代码惠及所有人。(我不会将文章本身公开,因为只有在 CodeProject 上提供文章有助于我个人以及 CodeProject 网站的可见度。)如果您在自己的应用程序中使用演示代码,如果您能发邮件告知我将不胜感激(仅为满足我对人们是否受益于我的代码的好奇心),但不是必需的。在您自己的源代码中注明出处也同样受欢迎,但不是必需的。

修订历史

2000 年 3 月 30 日:文章首次发布。
2000 年 6 月 6 日:更新了某些内容。;)
2006 年 5 月 17 日:更新以涵盖 VC 7.1 中的更改,清理了代码片段。

系列导航:« 第二部分 | 第四部分 »

© . All rights reserved.