关于桌面删除不掉的图标的实现

    最近朋友的电脑桌面上有了类似淘宝购物的桌面图标,普通的方法都不能删除,最后我使用了金山卫士将这个

可恶的图标清理掉了,同时对它的实现原理也产生了兴趣,几经摸索,终于实现了类似的功能,贴出来分享一下,第

一篇博文,加上水平有限,请多谅解。

    其实这个图标的技术实现是非常老的,甚至谈不上技术,但却让普通的用户很头痛,它利用了Shell命名空间扩展使得

该图标具备了类似于我的电脑,回收站等系统图标的特性,所以普通的方法无法删除。Shell命名空间扩展需要一个进程内

服务器来实现,这就要用到COM(组件对象模型),但原始C++实现COM非常麻烦,而ATL非常合适用来实现一个中小的

COM组件,趁这个机会,也接触了一下ATL。好了,废话少说,源码能说明任何问题,让我们来实际体验一下。

1:启动VS2005项目向导,选择ATL项目,在名字栏输入PersistIcon,接下来选择向导默认选项,这样

     向导就为我们生成了一个进程内服务器的ATL的框架。

2:在类视图里右击PersistIcon工程,选择添加菜单,类(C)...,在添加类属性选择ATL简单对象,点击添加,最后在

     简单对象向导里简称栏输入MyFolder,其它的内容会自动由向导填写.在第二步选择接口类型时选择自定义接口。

3: 打开MyFolder.h文档,向导已经为我们生成了一个组件类,并继承了必要的基类和IMyFolder接口。为实现命名空间

    扩展,我们需要实现几个接口,在头文件开始处添加(红色字体为手工添加的):

    #pragma once


   #include "resource.h"       // 主符号

   #include "PersistIcon.h"


   #include "shlobj.h"
   #include "comdef.h"

 

   在COM接口映射部分,添加我们需要实现的接口:

   BEGIN_COM_MAP(CMyFolder)
        COM_INTERFACE_ENTRY(IMyFolder)
        COM_INTERFACE_ENTRY(IShellFolder)                    
        COM_INTERFACE_ENTRY(IPersistFolder)               
        COM_INTERFACE_ENTRY(IShellExtInit)
        COM_INTERFACE_ENTRY(IContextMenu)

   END_COM_MAP()

   在类中声明以下接口函数:

  //IPersist
 //{624CD55D-B175-44D8-B772-16ADC850CAA1}
 STDMETHODIMP GetClassID(CLSID * pClassID);

 //IPersistFolder
 STDMETHODIMP Initialize(LPCITEMIDLIST pidl);

 //IShellFolder
 STDMETHOD (BindToObject) (LPCITEMIDLIST pidl,LPBC pbc,REFIID riid,VOID ** ppvOut);
 STDMETHOD (BindToStorage) (LPCITEMIDLIST pidl, LPBC pbc , REFIID riid, VOID ** ppvOut);
 STDMETHOD (CompareIDs) (LPARAM lParam,LPCITEMIDLIST pidl1,LPCITEMIDLIST pidl2);
 STDMETHOD (CreateViewObject) (HWND hwndOwner,REFIID riid,VOID ** ppvOut);
 STDMETHOD (EnumObjects) (HWND hwndOwner,SHCONTF grfFlags,IEnumIDList ** ppenumIdList);
 STDMETHOD (GetAttributesOf) (UINT cidl,LPCITEMIDLIST * apidl,SFGAOF * rgfInOut);
 STDMETHOD (GetDisplayNameOf) (LPCITEMIDLIST pidl,DWORD uFlags,LPSTRRET lpName);
 STDMETHOD (GetUIObjectOf) (HWND hwndOwner,UINT cidl,LPCITEMIDLIST * apidl,
  REFIID riid,UINT * rgfReserved,VOID ** ppVoid);
 STDMETHOD (ParseDisplayName) (HWND hwnd,LPBC pbc,LPOLESTR pwszDisplayName,
  ULONG * pchEaten,LPITEMIDLIST * ppidl,ULONG * pdwAttributes);
 STDMETHOD (SetNameOf) (HWND hwndOwner,LPCITEMIDLIST pidl,
  LPCOLESTR lpszName,DWORD uFlags,LPITEMIDLIST * ppidlOut);

 ///
 //IShellExtInit
 STDMETHOD (Initialize) (LPCITEMIDLIST lpidlFolder,IDataObject * pdtObject,HKEY hKeyProgID);

 ///
 //IContextMenu
 STDMETHOD (GetCommandString) (UINT_PTR idCmd,UINT uFlags,UINT * pwReserved,LPSTR lpszName,UINT cchMax);
 STDMETHOD (InvokeCommand) (LPCMINVOKECOMMANDINFO pici);
 STDMETHOD (QueryContextMenu) (HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags);

 在实现文件MyFolder.cpp中实现接口函数:

//IPersist
STDMETHODIMP CMyFolder::GetClassID(CLSID * pClassID)
{
   *pClassID = CLSID_MyFolder;

   return S_OK;
}


//IPersistFolder
STDMETHODIMP CMyFolder::Initialize(LPCITEMIDLIST pidl)
{
    return S_OK;
}

 

//IShellExtInit
STDMETHODIMP CMyFolder::Initialize(LPCITEMIDLIST lpidlFolder,IDataObject * pdtObject,HKEY hKeyProgID)
{
 return S_OK;
}

 

//IShellExtInit
STDMETHODIMP CMyFolder::Initialize(LPCITEMIDLIST lpidlFolder,IDataObject * pdtObject,HKEY hKeyProgID)
{
 return S_OK;
}

///
//IContextMenu
STDMETHODIMP CMyFolder::GetCommandString(UINT_PTR idCmd,UINT uFlags,UINT * pwReserved,LPSTR lpszName,UINT cchMax)
{
 return E_FAIL;
}

 

STDMETHODIMP CMyFolder::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
 if(HIWORD(pici->lpVerb) != 0)
  return E_INVALIDARG;
 switch(LOWORD(pici->lpVerb))
 {
 case 0:
  ShellExecute(NULL,TEXT("open"),TEXT("
http://blog.csdn.net/TheHappyWolf"),NULL,NULL,SW_SHOWNORMAL);
  break;
 case 1:
  MessageBox(pici->hwnd,TEXT("我不喜欢被删除!"),TEXT("提示"),MB_OK|MB_ICONEXCLAMATION);
 default:
  return E_INVALIDARG;
 }
 return S_OK;
}

 

STDMETHODIMP CMyFolder::QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)
{
  //插入打开菜单项
  InsertMenu(hMenu,indexMenu,MF_BYPOSITION,idCmdFirst+1,TEXT("删除(&D)"));
  InsertMenu(hMenu,indexMenu,MF_BYPOSITION,idCmdFirst,TEXT("打开(&O)"));

  return MAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_NULL,2);
}

其它的接口函数实现均返回E_NOTIMPL.

4:到这里我们的工作就算已经完成了,不过ATL在每次组件注册的时候,由于使用了rgs注册脚本,所以不够灵活。为了

   让我们的DLL能更灵活一些,我们需要修改其DLL中DllRegisterServer和DllUnregisterServer的实现。在PersistIcon.cpp

   包含文件之后添加如下数据结构定义:

#include "stdafx.h"
#include "resource.h"
#include "PersistIcon.h"
#include "shlobj.h"

 

//定义写入到注册表中的键
typedef struct _Key_Struct{
 LPCTSTR lpszSubKeyName;  //子键
 LPCTSTR lpszKeyName;  //键名
 DWORD dwType;    //键值类型
 
 union KEYVALUE{    //键值
  LPCTSTR strValue;  //字符串值
  DWORD dValue;   //整型值
 }KeyValue;
 
 //LPCTSTR strValue;
}KEYSTRUCT,*PKEYSTRUCT;

//显示的图标名称
LPCTSTR lpszFolderName = TEXT("Persist Icon");

 

class CPersistIconModule : public CAtlDllModuleT< CPersistIconModule >
{

     //内容略

}

 

修改DllRegisterServer和DllUnregisterServer的实现:

// DllRegisterServer - 将项添加到系统注册表
STDAPI DllRegisterServer(void)
{
    // 注册对象、类型库和类型库中的所有接口
    //HRESULT hr = _AtlModule.DllRegisterServer();
 HRESULT hr = S_OK;
 HKEY hKeyClass,hKeyClassSub,hKeyLocation;

 GetModuleFileName(hModule,szModuleName,MAX_PATH);
 TCHAR szIconLocation[MAX_PATH + 20] = {TEXT('/0')};
 _tcscat(szIconLocation,szModuleName);
 _tcscat(szIconLocation,TEXT(",0"));

 //注册命名空间的位置
 LPCTSTR lpszLocation = TEXT("SOFTWARE//Microsoft//Windows//CurrentVersion//Explorer//Desktop//NameSpace")
         TEXT("
//{624CD55D-B175-44D8-B772-16ADC850CAA1}");   //这里的组件CLSID需要根据你生成的实际更改,以后不再重述
 hr = RegCreateKeyEx(HKEY_LOCAL_MACHINE,lpszLocation,0,NULL,REG_OPTION_NON_VOLATILE,
   KEY_ALL_ACCESS,NULL,&hKeyLocation,NULL);
 if(hr != ERROR_SUCCESS)
  return hr;

 LPCTSTR lpszClassID = TEXT("CLSID//{624CD55D-B175-44D8-B772-16ADC850CAA1}");

 //自定义添加注册表项
 KEYSTRUCT myFolderKey[] = {
  {TEXT("InprocServer32"),NULL,REG_SZ,szModuleName},
  {TEXT("InprocServer32"),TEXT("ThreadingModel"),REG_SZ,TEXT("Apartment")},
  {TEXT("DefaultIcon"),NULL,REG_SZ,szIconLocation},
  {TEXT("Shellex//ContextMenuHandlers//0"),NULL,REG_SZ,TEXT("{624CD55D-B175-44D8-B772-16ADC850CAA1}")},
  {TEXT("Shellex//MayChangeDefaultMenu"),NULL,REG_SZ,TEXT("")},
  {TEXT("ShellFolder"),TEXT("Attributes"),REG_DWORD,0x00000000},
  {TEXT("ShellFolder"),TEXT("HideFolderVerbs"),REG_SZ,TEXT("")}
 };
 
 //删除CLSID键中的组件的类ID
 hr = SHDeleteKey(HKEY_CLASSES_ROOT,lpszClassID);
 
 //在CLSID中建立组件的类ID
 hr = RegCreateKeyEx(HKEY_CLASSES_ROOT,lpszClassID,0,NULL,REG_OPTION_NON_VOLATILE,
  KEY_ALL_ACCESS,NULL,&hKeyClass,NULL);
 if(hr != ERROR_SUCCESS)
  return hr;
 //设置类ID的默认值,该值将会显示为文件夹的名称
 hr = RegSetValueEx(hKeyClass,NULL,0,REG_SZ,
  (const BYTE *)(lpszFolderName),(_tcslen(lpszFolderName) + 1) * sizeof(TCHAR));
 if(hr != ERROR_SUCCESS)
 {//失败则删除所有的键
  SHDeleteKey(hKeyClass,NULL);
  RegCloseKey(hKeyClass);
  return hr;
 }
 
 //建立组件的CLSD下的每个键和键值
 for(int i = 0;i<(sizeof(myFolderKey))/(sizeof(KEYSTRUCT));i++)
 {
  HRESULT hr = RegCreateKeyEx(hKeyClass,myFolderKey[i].lpszSubKeyName,0,NULL,
   REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKeyClassSub,NULL);
  const BYTE * pData = (myFolderKey[i].dwType == REG_SZ?
            (const BYTE *)(myFolderKey[i].KeyValue.strValue):
                        (const BYTE *)(&(myFolderKey[i].KeyValue.dValue)));
  HRESULT hr1 = RegSetValueEx(hKeyClassSub,myFolderKey[i].lpszKeyName,0,myFolderKey[i].dwType,pData,
   (myFolderKey[i].dwType == REG_SZ)?(_tcslen(myFolderKey[i].KeyValue.strValue ) + 1) * sizeof(TCHAR):sizeof(DWORD));

  if(hr1!=ERROR_SUCCESS)
  {
   SHDeleteKey(hKeyClass,NULL);
   break;
  }

  if(hr == ERROR_SUCCESS)
   RegCloseKey(hKeyClassSub);
 }
 RegCloseKey(hKeyClass);

 //更新桌面
 LPITEMIDLIST lpidlDesktop;
 SHGetSpecialFolderLocation(NULL,CSIDL_DESKTOP,&lpidlDesktop);
 SHChangeNotify(SHCNE_UPDATEDIR,SHCNF_IDLIST,lpidlDesktop,0);
 //释放内存
 IMalloc * pMalloc;
 if(SHGetMalloc(&pMalloc) == S_OK)
 {
  pMalloc->Free(lpidlDesktop);
  pMalloc->Release();
 }

 return S_OK;
}


// DllUnregisterServer - 将项从系统注册表中移除
STDAPI DllUnregisterServer(void)
{
 //HRESULT hr = _AtlModule.DllUnregisterServer();
 HRESULT hr1,hr2;
 //删除组件类键
 hr1 = RegDeleteKey(HKEY_CLASSES_ROOT,TEXT("CLSID//{624CD55D-B175-44D8-B772-16ADC850CAA1}"));
 //删除命名空间位置键
 hr2 = RegDeleteKey(HKEY_LOCAL_MACHINE,
        TEXT("SOFTWARE//Microsoft//Windows//CurrentVersion//Explorer//Desktop//NameSpace")
        TEXT("
//{624CD55D-B175-44D8-B772-16ADC850CAA1}"));
 if(hr1!=ERROR_SUCCESS || hr2!=ERROR_SUCCESS)
  return S_FALSE;

 //更新桌面
 LPITEMIDLIST lpidlDesktop;
 SHGetSpecialFolderLocation(NULL,CSIDL_DESKTOP,&lpidlDesktop);
 SHChangeNotify(SHCNE_UPDATEDIR,SHCNF_IDLIST,lpidlDesktop,0);
 //释放内存
 IMalloc * pMalloc;
 if(SHGetMalloc(&pMalloc) == S_OK)
 {
  pMalloc->Free(lpidlDesktop);
  pMalloc->Release();
 }
 return S_OK;
}

 

5:生成工程就会发现桌面多了一个图标,可以进行测试是否正常工作。

6:如果需要将组件注册到别的机器上去,可以写一exe,将该DLL作为资源存放,exe运行过程中从资源生成

     DLL文件,并加载该DLL,调用DLL中调出的DllRegisterServer和DllUnRegisterServer进行注册和反注册

     操作。

     终于完成了,好累啊。对于SHELL命名空间扩展和COM的一些不明白的地方,可以参考ATL开发指南和Visual C++ Windows Shell Programming,网上有电子书可以下载。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值