Windows Shell编程实现右键菜单-VS2005,Win7 32位

来源:互联网 发布:linux安装vnc蓝屏 编辑:程序博客网 时间:2024/06/06 14:28

参考资料:

windows右键菜单开发--with vs2008(2005) or vc9(vc8)


最近项目中有利用IContexMenu实现右键菜单的例子,想试验一下,然而参照了很多资料,出现了很多错误,捣鼓了几天,还是没有出现想要的效果。

在这里,把程序的编写过程写下来,希望能得到熟悉Shell的高手指点。


编译工具:VS2005(据传说,各个编译工具的区别主要在于所用的SDK。VS2008用的是SDK3.5,VS2005用的是SDK2.0,两者差别不大。对于Shell编程来讲,VC6就已经足够了。只是VS2005(2008)会与VC6或者VS2003有所区别)。

开发环境:Win7 32位,中文旗舰版

期待功能:在Windows的右键菜单里加入自己的菜单,点击之后能够发出消息,会有消息响应。效果如图:


程序编写:

第一步:打开VS2005,新建ATL工程,命名为MyShell。各选项设置如图:





点击【完成】,完成项目的创建。


可以看到,在解决方案MyShell下,生成了两个工程:MyShell和 MyShellPS。



此时,编译报错。详见错误处理一


第二步:建立接口。


我们的dll是要加载到win外壳程序中的,这是通过注册表来实现的(后面讲具体实现方法),当右键点击弹出菜单前,系统会根据注册表信息判断要去加载哪些dll,当发现我们的dll时,加载,并根据shell的接口来实现我们定义的函数。


shell的接口是固定的,他不会在意你的函数是什么,只要你实现了他的接口,他就会去执行。



右键点击工程MyShell,选择【添加 类】,在向导中选择ATL->ATL简单对象(Simple Objec),点击【添加】。



在简称(Short Name)中写上你的接口名字,写为【MyMenu】,其他选项程序会自动填充。



此时点击【完成】就好。


这时候会看到,类视图区多了2个东西,一个CMyMenu的类,还一个IMyMenu的接口。




(可选)为接口添加方法(Add method)

右键单击IMyMenu,选择 添加方法(Add method),自定一个方法名(函数),比如'MyMenuMethod',参数类型自定义,看你程序需求了。



在方法MyMenuMethod',我只是做了简单的message

STDMETHODIMP CMyMenu::MyMenuMethod(void){AFX_MANAGE_STATE(AfxGetStaticModuleState());// TODO: 在此添加实现代码::MessageBox( NULL, _T("In MyMenuMethod"), _T("MyShell"), MB_OK);return S_OK;}

此时,可以编译一下。在不同编译环境下可能会出现不同问题,因此应该养成完成一项修改,编译一下的习惯。

得到出错信息如下:

error PRJ0050: 未能注册输出。请确保您有修改注册表的相应权限。

见错误处理,错误一的处理。

error C2787: 'IContextMenu' : no GUID has been associated with this object
error C2440: 'initializing' : cannot convert from 'DWORD_PTR' to 'const IID *'
error C2440: 'initializing' : cannot convert from 'ATL::_ATL_CREATORARGFUNC (__stdcall *)' to 'DWORD_PTR'

见错误处理,错误二的处理。



第三步:在CMyMenu的基类中添加IShellExtInit、IContextMenu。

要实现shell的所有接口,但ATL为我们自动添加的接口里不包含IShellExtInit、IContextMenu,所以要手动补充上。

<span style="color:#454545;">class ATL_NO_VTABLE CMyMenu :public CComObjectRootEx<CComSingleThreadModel>,public CComCoClass<CMyMenu, &CLSID_MyMenu>,public IDispatchImpl<IMyMenu, &IID_IMyMenu, &LIBID_MyShellLib, /*wMajor =*/ 1, /*wMinor =*/ 0></span><span style="color:#33cc00;">,public IShellExtInit,public IContextMenu</span>

并在宏声明那里添加2个接口

<span style="color:#454545;">BEGIN_COM_MAP(CMyMenu)COM_INTERFACE_ENTRY(IMyMenu)COM_INTERFACE_ENTRY(IDispatch)</span><span style="color:#33cc00;">COM_INTERFACE_ENTRY(IShellExtInit)COM_INTERFACE_ENTRY(IContextMenu)</span><span style="color:#454545;">END_COM_MAP()</span>

注意:新添加的两个接口基类,需要头文件支持,我们把这2个头文件加到总声明 stdafx.h 里。

#include <shlobj.h>#include <shobjidl.h>

在类视图中便会显示出CMyMenu类里的2个新接口。


此时编译会报错,因为接口没有被实现。

错误 1 error C2259: 'ATL::CComObject<Base>' : cannot instantiate abstract classc:\program files\microsoft visual studio 8\vc\atlmfc\include\atlcom.h1792


第四步: 实现接口

1. IShellExtInit  鼠标选中它,可以看到它里面只有一个纯虚函数



函数原型为:

<pre name="code" class="cpp">        virtual HRESULT STDMETHODCALLTYPE Initialize(             /* [in] */ LPCITEMIDLIST pidlFolder,            /* [in] */ IDataObject *pdtobj,            /* [in] */ HKEY hkeyProgID) = 0;


LPCITEMIDLIST  这个类型名2008和2005是有区别的,2008中会显示PCIDLIST_ABSOLUTE,其实他们是本质是一个东西(#define   PCIDLIST_ABSOLUTE     LPCITEMIDLIST),他是指向一个ITEMIDLIST的结构体指针。这个结构体标示了右键点击时对应的文件夹。如果属性页扩展则这个值为NULL即可。

pdtobj 指向 IDataObject 接口的指针。


在我们的类中重写这个虚函数,直接把定义复制过来即可,用来做些初始化动作。

MyMenu.h中:

virtual HRESULT STDMETHODCALLTYPE Initialize( /* [in] */ LPCITEMIDLIST pidlFolder,/* [in] */ IDataObject *pdtobj,/* [in] */ HKEY hkeyProgID);

(由于在objbase.h中有如下定义:

define STDMETHOD(method)       virtual HRESULT STDMETHODCALLTYPE method

因此上述代码也可写为:

        STDMETHOD(Initialize)(             /* [in] */ LPCITEMIDLIST pidlFolder,            /* [in] */ IDataObject *pdtobj,            /* [in] */ HKEY hkeyProgID) = 0;)
)


MyMenu.cpp中:

HRESULT STDMETHODCALLTYPE CMyMenu::Initialize( /* [in] */ LPCITEMIDLIST pidlFolder,/* [in] */ IDataObject *pdtobj,/* [in] */ HKEY hkeyProgID){return S_OK;}

(由于在objbase.h中存在如下定义,

#define STDMETHODIMP            HRESULT STDMETHODCALLTYPE
因此上述代码也可写为

STDMETHODIMP CMyMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID){return S_OK;}

2.IContextMenu: 这里有3个接口,方法同上,鼠标左键单击点中,可以查看到。

GetCommandString      InvokeCommand    QueryContextMenu

直接在把基类中的声明复制粘贴过来,将这3个都实现。暂时都直接返回 return S_OK。

目前为止我们已经重写了4个虚函数(没有其他代码,只简单的return S_OK;),所以框架结构已经完全建立好了。点击 编译,查看下有没有笔误之类的错误。



第四步: 补充代码。

要补充的就是上面4个接口函数而已,在补充之前我们要清楚他们分别是做什么用的。

1.Initialize

顾名思义。。。初始化用的。此文章不做介绍,简单来说在这里可以得到当前文件(夹)路径。具体可以参照msdn。


2.GetCommandString

再次顾名思义下,获得右键菜单命令信息,包括帮助信息,命令的名称(详见MSDN)


STDMETHODIMP CMyMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT * pwReserved, LPSTR pszName, UINT cchMax){if ( (idCmd != 102) && (idCmd != 103)){return E_INVALIDARG;}lstrcpynA(pszName, _T"This is a test info", cchMax);return S_OK;}


这个接口没有实际意义,我们甚至可以不深究它。

idCmd是传进来的菜单命令id的偏移量。 这个值是我们自己定义给要添加菜单的值。 102 和 103都是这样,当点击我们添加的菜单后,命令id的偏移量肯定是我们自定义的,后面会介绍,如果不是,将会返回错误,return E_INVALIDARG。如果是,则向参数 pszName 赋值。返回S_OK。


3.QueryContextMenu

想菜单中添加内容,详见msdn。

这个函数是最为关键的函数之一。(其实关键函数有2个,下面那个就是另一个之一)

要增加几个菜单,在什么位置,叫什么名字,带不带图标,点击后命令id等等都是这里完成的。

虽然东西说起来挺多,但是函数确很少。。。。对,就一个函数!


InsertMenu,注意,我们这里并没有用到mfc。

WINUSERAPIBOOLWINAPIInsertMenuW(    __in HMENU hMenu,    __in UINT uPosition,    __in UINT uFlags,    __in UINT_PTR uIDNewItem,    __in_opt LPCWSTR lpNewItem);


STDMETHODIMP CMyMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags){InsertMenu(hmenu,indexMenu,MF_SEPARATOR|MF_BYPOSITION,0,NULL);//分割线InsertMenu(hmenu, indexMenu+1, MF_STRING | MF_BYPOSITION, idCmdFirst + 102, _T("XXXX"));InsertMenu(hmenu, indexMenu+2, MF_STRING | MF_BYPOSITION, idCmdFirst + 103, _T("YYYY"));InsertMenu(hmenu,indexMenu+3,MF_SEPARATOR|MF_BYPOSITION,0,NULL);//分割线 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(103 + 1));}

InsertMenu 这个函数大家应该不陌生,初学mfc时候应该有学过类似的,关于菜单操作MF_BYPOSITION还是MF_BYCOMMAND,这些mfc中的菜单操作也用到过。idCmdFirst 是系统传进来的id首值,我们自定义一个偏移量,表示我们的菜单id(这里用了102,103),最后将下一个命令id返回给系统,告诉系统后面的菜单的命令从哪里开始偏移。

(可选)在这里我们可以给菜单前面加个图:将图导入资源(bitmap),或是自己画一个,注意大小不要超过13x13

LPTSTR lptStr =MAKEINTRESOURCE( IDB_BITMAP1);HBITMAP hBitmap=LoadBitmapW(ghInstance,lptStr);//gHinstance 是一个HINSTANCE类型的全局变量,在dll入口函数(DllMain)中,将系统传进来的参数hInstance保存。

</pre><pre name="code" class="cpp" style="color: rgb(69, 69, 69); font-size: 14px; line-height: 21px; "><pre name="code" class="cpp">LPTSTR lptStr =MAKEINTRESOURCE( IDB_BITMAP1);HBITMAP hBitmap=LoadBitmapW(ghInstance,lptStr);//gHinstance 是一个HINSTANCE类型的全局变量,在dll入口函数(DllMain)中,将系统传进来的参数hInstance保存。SetMenuItemBitmaps(hmenu,indexMenu+1,MF_BYPOSITION,hBitmap,hBitmap); 


4.InvokeCommand   

 关键函数的另一个之一,定义菜单的消息响应。

请msdn下查下CMINVOKECOMMANDINFOEX里的 lpVerb参数的意思,你会发现他的高位标示了是字符串还是命令id偏移量。我们对字符串不关心,我们关心的是命令id. 这个函数是要实现具体点击菜单后所要实现的功能。所以判断是点了 XXXX(102),还是YYYY(103)菜单呢。

<span style="font-family:tahoma, helvetica, arial;">STDMETHODIMP CMyMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici){if(HIWORD(((CMINVOKECOMMANDINFOEX *) lpici)->lpVerbW)){return S_OK;}else{if(LOWORD(lpici->lpVerb)==102){MessageBox(NULL,_T("1111"),_T("MyShell"),MB_OK);}if(LOWORD(lpici->lpVerb)==103){MessageBox(NULL,_T("2222"),_T("MyShell"),MB_OK);}}return S_OK;}</span>

在这里也可以用先前添加的 接口方法,比如在103里注释掉messagebox,换成MenuFuctionb();可以试试。


至今为止,代码部分已经编写完成。编译一下,生成dll。

下面可以看一下怎么注册我们的dll。


注册DLL

截止到目前,我们所有shell扩展接口都实现了,编译(release版),讲dll考入客户机(可以是虚拟机,或者你自己的机器)。

1、首先是注册我们的dll

在cmd里写 

Regsvr32 MyShell.dll
 (这么写的前提是MyShell.dll所在位置是系统盘的system32文件夹下,如果没在这里,你可以把全路径写上)


2、然后是修改注册表

我们想让所有文件夹和文件点击右键时候都看到我们的菜单。

注册表位置:HKEY_CLASSES_ROOT -> *  -> shellex -> ContextMenuHandlers添加新键 MyShell (这个名字你随意起)然后设置默认键值为 REG_SZ 类型,数据写 {xxxx.........} 具体数据写什么呢,当然是我们产品的guid,他的位置在我们程序中,新建工程时候vc已经帮我们定义好了,他是一个唯一(概率上来讲)的值。

在解决方案视图中,源文件目录下,找到MyShell. idl文件,打开查看, library 下的 uuid就是我们要的号码,把它复制下来,



我这里是{11B5203F-0C25-47E5-AAAC-9C959E46BCAE},你那里肯定不是这个数,因为他是vc随机生成的,目的就是为了避免产品间重名冲突。所以我们的注册表修改完后应该是这个样子的



她不包括文件夹。所以我们还要修改文件夹的信心。注册表位置:HKEY_CLASSES_ROOT-->Directory --> shellex--> ContextMenuHandlers建立一个 MyShell键,设置值与‘*’相同





目前为止都操作完毕了,对于注册表操作,程序员最好去写一个简单的安装程序,win控制台程序就可以,用我们的函数来进行注册dll和修改注册表。当然想对应的你要写好你的卸载程序,去移除这些注册表。



调试方法,待续……


0 0