CSDN博客

img mefish

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

发表于2001/10/8 17:48:00  1033人阅读

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

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

    查询信息扩展(The QueryInfo extension)

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

     [My Computer tooltip - 6K]

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

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

     [Word doc tooltip - 5K]

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

     [WinZip tooltip - 5K]

        我已经找到的最佳的文档是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 复选框,然后单击完成。我们现在就有了一个空的将会生成DLLATL项目,但是我们必须添加自己的外壳扩展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

        所以我们需要添加IPersistFileCTxtInfoShlExt 实现的接口列表中。打开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_NOTIMPLGetInfoTip()是我们返回给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)中。

     [text file tooltip - 6K]

    注册外壳扩展

        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/

0 0

相关博文

我的热门文章

img
取 消
img