外壳扩展编写完全傻瓜指南(三)(Michael Dunn)

类别:VC语言 点击:0 评论:0 推荐:
Download demo project - 11 Kb

    在指南的第一部分和第二部分,我向大家演示了如何编写上下文菜单扩展。在第三部分,我将燕是一种新的扩展类型,向大家解释如何共享外壳的内存,并且演示如何在ATL之外使用MFC。

    第三部分假设你已经知道了外壳扩展的基本知识(在第一部分中解释了),而且你对MFC很熟悉。要注意的是这儿的扩展需要4.71或者更高版本的扩展,所以你必须是运行Windows 98 或 2000,或者在95/NT4 上装有活动桌面(Active Desktop)。

查询信息扩展(The QueryInfo extension)

    活动桌面(Active Desktop)引进了一个新特征,如果你的鼠标在特定的对象上悬停的话,工具条提示会显示对象的描述。比如说,在“我的电脑”上悬停,就会出现如下的工具提示:

    其它一些对象比如说“网上邻居”和“控制面板”也有相似的提示。我们也可以通过查询信息扩展(QueryInfo extension)为其他一些对象提供我们自己的工具提示。

    关于查询信息扩展(QueryInfo extension)的说明是:这是我命名的;我这么称呼他是因为他用了这么一个借口:IQueryInfo 。到现在我可以说,它还没有一个官方名字。我快速的查看了一下1999年十月的MSDN,甚至没有提到这个扩展。 很明确它是一个被支持的扩展,因为微软的Office也为它的文件类型安装了QueryInfo扩展,如下所示:

    WinZip 版本8也有一个为压缩文件安装的QueryInfo扩展:

    我已经找到的最佳的文档是Dino Esposito的在2000年3月的MSDN杂志上的文章“Enhance Your User's Experience with New Infotip and Icon Overlay Shell Extensions(使用新的信息提示和图标覆盖外壳扩展来增强你的用户的体验)”。

QueryInfo扩展的开始?它能做什么?

    这个外壳扩展将是一个快速文本文件查看器——它将显示文件大小和文件的第一行的内容。当用户在一个TXT文件上悬停鼠标的时候,我们的信息将在工具提示(tooltip)上显示。

用AppWizard 开始

运行AppWizard ,做一个新的ATL COM wizard app。我们叫它TxtInfo 。因为我们这次要使用MFC,所以请选中Support MFC 复选框,然后单击完成。我们现在就有了一个空的将会生成DLL的ATL项目,但是我们必须添加自己的外壳扩展COM对象。在ClassView树中,右键单击 TxtInfo classes项,选择New ATL Object。

在ATL Object 向导,第一面板已经选择了Simple Object ,只要单击下一步就行了。在第二面板中,在Short Name 编辑控件中输入TxtInfoShlExt ,然后单击确定(面板中的其它的编辑框将会自动完成)。这就创建了一个类名为CTxtInfoShlExt 的类,它包含了实现一个COM对象的基本代码。我们将向这个类添加我们的代码。

如果你看一下ClassView树的的时候,你将会发现我们有一个从CWinApp派生的CTxtInfoApp类。这个类和全局变量theApp的出现使我们使用MFC成为可能,正如我们编写一个没有ATL的普通MFC DLL一样。

接口实现

    以前,在我们的上下文菜单扩展中(context menu extensions),我们实现Explorer实现我们对象的IShellExtInit接口。对外壳扩展来说,还有另一个实现接口,IPersistFile这是一个QueryInfo 扩展使用的接口。为什么会不同?如果你还记得的话,IShellExtInit::Initialize()接收一个IDataObject 指针,通过该指针我们可以枚举被选中的文件。通过IPersistFile扩展只能对单个文件进行操作。因为鼠标不可能在同一时间在两个对象上悬停,所以QueryInfo扩展一次只在一个文件上工作,所以它使用IPersistFile 。

    所以我们需要添加IPersistFile到CTxtInfoShlExt 实现的接口列表中。打开TxtInfoShlExt.h,然后添加下面红色的行:

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

    我们还需要一个变量来存储Explorer在我们实现过程中的文件名:

protected: // ITxtInfoShlExt CString m_sFilename;

    注意的是我们现在可以在任何地方使用一个MFC对象。

    如果你查看IPersistFile的文档,你会发现有很多的方法。幸运的是,对本文的扩展,我们仅仅需要实现Load()而忽略其他的。下面是IPersistFile的方法的原型。

public: // IPersistFile STDMETHOD(GetClassID)(LPCLSID) { return E_NOTIMPL; } STDMETHOD(IsDirty)() { return E_NOTIMPL; } STDMETHOD(Load)(LPCOLESTR, DWORD); STDMETHOD(Save)(LPCOLESTR, BOOL) { return E_NOTIMPL; } STDMETHOD(SaveCompleted)(LPCOLESTR) { return E_NOTIMPL; } STDMETHOD(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 if necessary. m_sFilename = wszFilename; return S_OK; }

    注意函数的第一行。要使MFC工作正常,这行是必需的。因为我们的DLL被非MFC程序装载,每个使用MFC的出口函数必须人工初始化MFC。如果不包括这一行,很多的MFC函数(大多是和资源相关的)将会中断或产生断言。

    文件名被保存在m_sFilename以备后用。注意,我使用了CString赋值操作符转换字符串到ANSI的优点,如果这个DLL是作为ANSI建立的话。

创建工具提示文本

    在Explorer调用我们的Load()方法之后,它调用QueryInterface()来获得另一个接口:IQueryInfo 。IQueryInfo是一个相当简单的接口,仅有两个方法(实际上我们只是用了其中一个)。再次打开TxtInfoShlExt.h ,添加如下红颜色的行:

class ATL_NO_VTABLE CTxtInfoShlExt : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>, public IDispatchImpl<ITxtInfoShlExt, &IID_ITxtInfoShlExt, &LIBID_TXTINFOLib>, public IPersistFile, public IQueryInfo { BEGIN_COM_MAP(CTxtInfoShlExt) COM_INTERFACE_ENTRY(ITxtInfoShlExt) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IPersistFile) COM_INTERFACE_ENTRY(IQueryInfo) END_COM_MAP()

    然后添加IQueryInfo的方法:

// IQueryInfo STDMETHOD(GetInfoFlags)(DWORD*) { return E_NOTIMPL; } STDMETHOD(GetInfoTip)(DWORD, LPWSTR*);

    GetInfoFlags()方法目前不被应用,我们仅仅返回E_NOTIMPL。 GetInfoTip()是我们返回给Explorer并让它显示在工具条提示上的实现之处。首先是讨厌的模块:

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

    再次的,为了初始化MFC,AFX_MANAGE_STATE 首先被调用。这必须是在函数开始就被做,甚至在变量定义之前,因为很多的构造器是调用了MFC函数。

    dwFlags 目前未被应用。ppwszTip 是一个指向一个LPWSTR 的指针(Unicode字符串指针),我们设置它指向我们必须分配的缓存。

    第一步,我们将试图打开文件。我们知道它的文件名,因为我们早先在Load()函数中将它存储了。

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

    现在,由于我们需要使用外壳内存分配器来分配一个缓存,我们需要一个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字节长。下一步是创建工具提示(tooltip)的第一部分,列出了文件的大小:

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

    现在,如果我们能够阅读文件的第一行,把它添加到工具提示(tooltip)中。

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

    现在,我们已经完成了工具提示。我们需要分配一个缓存。这儿我们使用IMalloc 。由SHGetMalloc()返回的指针是外壳IMalloc接口的一个拷贝。任何使用那个接口分配的内存都存在于外壳的进程空间中,因此外壳可以使用它。更重要的,外壳也可以释放它。所以我们要做的是分配缓存,然后就忘了它吧。外壳在使用完了之后会释放它的。

    另外一件需要意思到的是我们返回给外壳的字符串必须是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中的字符串,并把它显示在工具提示(tooltip)中。

注册外壳扩展

    QueryInfo扩展的注册和上下文菜单扩展稍有不同。我们的扩展在HKEY_CLASSES_ROOT 的子键下注册,它的名字是我们想处理的文件扩展名。在这篇文章里,它是HKCR\.txt 。不过等一等,好像有点奇怪!你可能认为ShellEx 的子键看起来应该像"TooltipHandlers"之类的样子。但是不是!这个键被叫做 "{00021500-0000-0000-C000-000000000046}"。

    在这儿,我想微软可能试图越过我们偷偷摸摸做些外壳扩展。如果你看看注册表的话,你会发现其它的ShellEx 的子键的名称也是GUID。上面的GUID正好是IQueryInfo的GUID。

    不管怎样,这是是我们的扩展被.TXT文件调用的必须脚本:

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

    你可以通过复制上面的这段代码,然后改变".txt"为任何你想要的扩展名来使扩展被其他扩展名的文件调用。不幸的是,你不能在*或者 AllFileSystemObjects下注册达到能被所用文件调用的目的。

    正如我们在前面的扩展中所讲的一样,在NT/2000下,我们需要添加我们的扩展到“认证的(approved)”扩展列表当中。实现这个过程的代码在 DllRegisterServer()和DllUnregisterServer()函数中。

待续...

    在第四部分中,我们将回到上下文菜单中去看看一种新的扩展,拖和扔(drag and drop)处理。我们也将看到更多的MFC的使用。

    你可以从下面的网址获得这个和其他文章的最新版本:http://home.inreach.com/mdunn/code/

本文地址:http://com.8s8s.com/it/it2966.htm