windows外壳扩展编程之windows右键菜单

 

第一部分 SHELL基本概念

    Windows外壳扩展(Windows Shell Extension),是一类特殊的COM对象,在这类COM对象中用户可以加入自己的特殊功能,而Windows外壳扩展最终都会被Windows Explorer所引用[1]

    A shell extension is a COM object that adds some kind of functionality to the Windows shell (Explorer).

    There are two parts in the term "shell extension." Shell refers to Explorer, and extension refers to code you write that gets run by Explorer when a predetermined event happens (e.g., a right-click on a .DOC file). So a shell extension is a COM object that adds features to Explorer.[7]

    A shell extension is an in-process server that implements some interfaces that handle the communication with Explorer. ATL is the easiest way to get an extension up and running quickly, since without it you'd be stuck writing QueryInterface() and AddRef() code over and over. It is also much easier to debug extensions on Windows NT-based OSes, as I will explain later.

    “Shell 扩展从字面上分两个部分:Shell ExtensionShell Windows Explorer,而Extension 则指由你编写的当某一预先约定好的事件(如在以. doc 为后缀的文件图标上单击右键)发生时由 Explorer调用执行的代码。因此一个“Shell 扩展就是一个为 Explorer 添加功能的 COM 对象。

    动态库必须注册才能使用。除了使用 regasm 来注册 DLL 以外,还应该在代码中增加 RegisterServer UnregisterServer 方法,以指导 DLL 注册时,在 Windows 注册表中增加什么键。关于具体键以下做简单说明:

    1) 注册DLLShell Extensions。具体位置是 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved,增加以 GUID 为名称的键,值则是动态库说明。(此位置里面全是 Shell 扩展的动态库注册,许多相关软件就是从里面获取信息,例如 ShexView

    2) 关联文件。Shell扩展一般是针对文件或者文件夹的,因此必须关联;许多人都熟知“HKEY_CLASSES_ROOT\*”的作用,就是用来关联所有文件。而文件夹则是“HKEY_CLASSES_ROOT\Folder”[3]

    我们所看到的资源管理器以及整个桌面,都是一个 Shell。在win32中是以外壳名字空间的形式来组织文件系统的,在外壳名字空间里的每一个对象()都实现了一个IShellFolder的接口,通过这个接口我们可以直接查询或间接得到其他相关的接口。

    (注:这里的对象指的是外壳名字空间中的一个节点,对象有可能是一个文件夹,有可能是一个文件,也有可能是一个虚拟文件夹,例如:我的电脑,网上邻居,控制面板等)[4]

    A shell extension is an in-process server that implements some interfaces that handle the communication with Explorer. ATL is the easiest way to get an extension up and running quickly, since without it you'd be stuck writing QueryInterface() and AddRef() code over and over. It is also much easier to debug extensions on Windows NT-based OSes[7]

    在外壳编程中,要使用 PIDL 路径代替普通路径桌面是最顶级的文件夹,外壳名字空间中其他各项都可以用从桌面开始的 PIDL 加以表示。

通过API SHGetDesktopFolder获取桌面PIDL 和其 IShellFolder 接口。

通过桌面,来获取“C:\”这个路径的 PIDL IShellFolder 接口,可以通过 IShellFolder ParseDisplayName BindToObject 函数。

 

第二部分 Windows SHELL编程

    There are many types of shell extensions, each type being invoked when different events happen. Here are a few of the more common types, and the situations in which they are invoked:[7]

Type

When it's invoked

What it does

Context menu handler

User right-clicks on a file or folder. In shell versions 4.71+, also invoked on a right-click in the background of a directory window.

Adds items to the context menu.

Property sheet handler

Properties dialog displayed for a file.

Adds pages to the property sheet.

Drag and drop handler

User right-drags items and drops them on a directory window or the desktop.

Adds items to the context menu.

Drop handler

User drags items and drops them on a file.

Any desired action.

QueryInfo handler (shell version 4.71+)

User hovers the mouse over a file or other shell object like My Computer.

Returns a string that Explorer displays in a tooltip.

Before we begin coding, there are some tips that will make the job easier. When you cause a shell extension to be loaded by Explorer, it will stay in memory for a while, making it impossible to rebuild the DLL.

To have Explorer unload extensions more often, create this registry key:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

and set the default value to "1". On 9x, that's the best you can do. On NT, go to this key:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer and create a DWORD called DesktopProcess with a value of 1. This makes the desktop and Taskbar run in one process,

and subsequent Explorer windows each run in its own process. This means that you can do your debugging with a single Explorer window, and when you close it, your DLL is automatically unloaded, avoiding

any problems with the file being in use. You will need to log off and back on for these changes to take effect.[7]

第一:注册表编程。第二:Shell Extension COM编程。通过注册表方式实现

其实十分简单,请参阅 COM[1]组件注册表实现。在以下的内容为 shell 扩展编程---" Context Menu 处理器。

Shell扩展实例均为进程内组件,它们均以动态库的形式存在。

When our shell extension is loaded, Explorer calls our QueryInterface() function to get a pointer to an IShellExtInit interface.[7]

IShellExtInit接口:IShellExtInit 接口为 Shell 扩展编程必须要实现的接口。该接口主要用来初始化 Shell 扩展处理器,它仅有一个虚成员函数Initialize,用户所有的 Shell 扩展初始化动作都由该函数完成。

Initialize 函数中,我们要做的事情就是获取用户鼠标右键点击的文件名称。当用户在一个拥有WS_EX_ACCEPTFILES风格的窗体中Drag/Drop文件时这些文件名会以同一种格式存储,而且文件完整路径的获取也都以DragQueryFile API函数来实现。但是DragQueryFile需要传入一个HDROP句柄,该句柄即为Drag/Drop文件名称列表数据句柄(开始存放数据的内存区域首指针)。而 HDROP句柄的可以通过接口"DATAOBJECT lpdobj"的成员函数"GetData"来获取。

进程内组件编程的一些特点,大体总结如下:"新建自己的接口,然后继承某些接口,最后一一实现这些接口的所有虚成员函数或加入自己的成员函数,最后就是组件的注册"

1) 初始化接口

int Initialize(IntPtr pidlFolder, IntPtr lpdobj, uint hKeyProgID);

HRESULT IShellExtInit::Initialize (

LPCITEMIDLIST pidlFolder,

LPDATAOBJECT pDataObj,

HKEY hProgID )

Explorer 使用该方法传递给我们各种各样的信息.

Explorer uses this method to give us various information. pidlFolder is the PIDL of the folder containing the files being acted upon. (A PIDL [pointer to an ID

list] is a data structure that uniquely identifies any object in the shell, whether it's a file system object or not.) pDataObj is an IDataObject interface

pointer through which we retrieve the names of the files being acted upon. hProgID is an open HKEY which we can use to access the registry key containing

our DLL's registration data. For this simple extension, we'll only need to use the pDataObj parameter.[7]

pidlFolder 是用户所选择操作的文件所在的文件夹的PIDL变量(一个PIDL[指向ID列表的指针],是一个数据结构,它唯一地标识了在Shell命名空间的任何对象,一个Shell命名空间中的对象可以是也可以不是真实的文件系统中的对象。)lpdobj是一个IDataObject接口指针,通过它我们可以获取用户所选择操作的文件名。hKeyProgID是一个HKEY注册表键变量,可以用它获取我们的DLL的注册数据。

因此我们可以在这个方法中,获取到被右击选择的一个或多个文件/文件夹名。

The COM_MAP is how ATL implements QueryInterface(). It tells ATL what interfaces other programs can retrieve from our COM objects.[7]

2) 与上下文菜单交互的接口

Once Explorer has initialized our extension, it will call the IContextMenu methods to let us add menu items, provide fly-by help, and carry out the user's selection.[7]

一旦 Explorer 初始化了扩展,它就会接着调用 IContextMenu 的方法让我们添加菜单项, 提供状态栏上的提示, 并响应执行用户的选择。

处理器类型 COM接口

Context menu 处理器 IContextMenu

Property sheet 处理器 IShellPropSheetExt

Drag and drop 处理器 IContextMenu

Drop 处理器 IDropTarget

QueryInfo 处理器(Shell V4.71+) IQueryInfo

其中的"Drag and drop 处理器"除了COM接口IContextMenu需要实现外还得需要注册表的特殊注册才行。IContextMenu 接口有三个虚成员函数需要我们的组件来实现:

The first one, QueryContextMenu(), lets us modify the menu.[7]

QueryContextMenu,在QueryContextMenu 成员函数中我们可以加入自己的菜单项,通过 InsertMenu API 函数来实现。

HRESULT IContextMenu::QueryContextMenu (

HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd,

UINT uidLastCmd, UINT uFlags );

hmenu is a handle to the context menu. uMenuIndex is the position in which we should start adding our items. uidFirstCmd and uidLastCmd are the range of

command ID values we can use for our menu items. uFlags indicates why Explorer is calling QueryContextMenu(), and I'll get to this later.[7]

3) 在状态栏上显示提示帮助

The next IContextMenu that can be called is GetCommandString(). If the user right-clicks a text file in an Explorer window, or selects a text file and then clicks the File menu, the status bar will show fly-by help when our menu item is highlighted. Our GetCommandString() function will return the string that we want Explorer to show.

GetCommandStringGetCommandString 成员函数为 Explorer 提供了在状态栏显示菜单命令提示信息的方法。在这个方法中 "LPSTR pszName" 是我们要关注的参数,我们只要根据 "UINT uFlags" 参数来填充 "LPSTR pszName" 参数即可。在COM 编程中尽可能使用兼容的 TCHAR 类型,也尽量不要使用C类函数库,因为这样会使您无法通过 "Win32 Release Mindependency " 或其他 UINCode/Release 版本的编译过程。

HRESULT IContextMenu::GetCommandString (

UINT idCmd, UINT uFlags, UINT* pwReserved,

LPSTR pszName, UINT cchMax );

idCmd is a zero-based counter that indicates which menu item is selected. Since we have just one menu item, idCmd will always be zero. But if we had added, say, 3 menu items,

idCmd could be 0, 1, or 2. uFlags is another group of flags, We can ignore pwReserved. pszName is a pointer to a buffer owned by the shell where we will store

the help string to be displayed. cchMax is the size of the buffer. The return value is one of the usual HRESULT constants, such as S_OK or E_FAIL.[7]

GetCommandString() can also be called to retrieve a "verb" for a menu item. A verb is a language-independent string that identifies an action that can be taken on a file. The docs for ShellExecute() have more to say, and the subject of verbs is best suited for another article, but the short version is that verbs can be either listed in the registry

(such as "open" and "print"), or created dynamically by context menu extensions. This lets an action implemented in a shell extension be invoked by a call to ShellExecute().

Anyway, the reason I mentioned all that is we have to determine why GetCommandString() is being called. If Explorer wants a fly-by help string, we provide it. If Explorer is asking for a verb, we'll just ignore the request. This is where the uFlags parameter comes into play. If uFlags

has the GCS_HELPTEXT bit set, then Explorer is asking for fly-by help. Additionally, if the GCS_UNICODE bit is set, we must return a Unicode string.[7]

下一个要被调用的IContextMenu 方法是 GetCommandString().。如果用户是在浏览器窗口中右击文本文件,或选中一个文本文件后单击文件菜单时,状态栏会显示提示帮助。我们的 GetCommandString() 函数将返回一个帮助字符串供浏览器显示。

4)执行用户的选择

InvokeCommand,实现最终菜单项命令的执行。

This method is called if the user clicks on the menu item we added. The prototype for InvokeCommand() is:

HRESULT IContextMenu::InvokeCommand (

LPCMINVOKECOMMANDINFO pCmdInfo );

IContextMenu 接口的最后一个方法是 InvokeCommand()。当用户点击我们添加的菜单项时该方法将被调用。其参数:CMINVOKECOMMANDINFO 结构带有大量的信息, 但我们只关心 lpVerb hwnd 这两个成员。

lpVerb performs double duty - it can be either the name of the verb that was invoked, or it can be an index telling us which of our menu items was clicked on. hwnd is the handle of the Explorer

window where the user invoked our extension; we can use this window as the parent window for any UI that we show.[7]

lpVerb参数有两个作用------它或是可被激发的verb(动作),或是被点击的菜单项的索引值。hwnd 是用户激活我们的菜单扩展时所在的浏览器窗口的句柄。我们可以根据被点击的菜单项索引,来执行相应的操作。

5)注册Shell扩展

现在我们已经实现了所有需要的COM接口. 可是我们怎样才能让浏览器使用我们的扩展呢?首先,我们要注册动态库。但仅仅这样是不够的,为了告诉浏览器使用我们的扩展, 我们需要在文本文件类型(因为我们要关联文本)的注册表键下注册扩展。ATL automatically generates code that registers our DLL as a COM server, but that just lets other apps use our DLL. In order to tell Explorer our extension exists, we need to register it under the key that holds info about text files[7]

The NoRemove keyword means that the key should not be deleted when the server is unregistered,ForceRemove, which means that if the key exists, it will be deleted

before the new key is written.

 

第三部分 SHELL Additional 

一、编程中的一些关键点 

1、 We implemented the IShellExtInit interface, which was how Explorer initialized our object. There is another initialization interface used for some shell extensions, IPersistFile, and this is the one an infotip extension uses. IShellExtInit::Initialize() receives an IDataObject pointer with which it can enumerate all of the files that were selected. Extensions that can only ever operate on a single file use IPersistFile. Since the mouse can't hover over more than one object at a time, an infotip extension only works on one file at a time, so it uses IPersistFile. 

2、AFX_MANAGE_STATE(AfxGetStaticModuleState()); // init MFC

The AFX_MANAGE_STATE macro is necessary for MFC to work properly. Because our DLL is being loaded by a non-MFC app, every exported function that uses MFC must initialize MFC manually. If you don't include that line, many MFC functions (mostly the ones related to resources) will break or have assertion failures.

3、In order to have a dialog which has the XP theme enabled, it was necessary to set the following values in stdafx.h before any of the #includes:

#define VC_EXTRALEAN //necessary

#ifndef _WIN32_WINNT

#define _WIN32_WINNT 0x501

#endif

#define _ATL_APARTMENT_THREADED

#define ISOLATION_AWARE_ENABLED 1 //XP style awareness

4、A shell extension is a COM DLL。

5、Note that DllRegisterServer() and DllUnregisterServer() are not called by Explorer, but by the installer. If you downloaded the source files instead of the installer, you will have to register the DLL yourself using the command line tool regsvr32. If you compile the source from within Visual C++, then regsvr32 runs as part as the build process.

6、整体而言,使用SHELL时流程基本如下:

In short, this is what happens: When the user right-clicks inside an Explorer window, Explorer calls CtxMenu's Initialize() function. At this point, the context menu isn't visible yet. Explorer then calls the QueryContextMenu() function to add the Select... item to the menu, and shows it. When the user moves over this menu item, Explorer obtains a description from GetCommandString(). Finally, when the user picks the Select... item, Explorer calls InvokeCommand().

The only place where we can figure out whether the user clicked on a file or on the background of the window is in Initialize().

http://www.codeproject.com/KB/shell/wildcardselect.aspx

http://www.allyoursoftware.com/

第四部分 相关的API

1、GlobalLock Function

Locks a global memory object and returns a pointer to the first byte of the object's memory block.

2、DragQueryFile Function

Retrieves the names of dropped files that result from a successful drag-and-drop operation.

http://msdn.microsoft.com/en-us/library/bb776408%28VS.85%29.aspx

3、GetProcAddress Function

Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).

http://msdn.microsoft.com/en-us/library/ms683212%28VS.85%29.aspx

4、InsertMenu Function

Inserts a new menu item into a menu, moving other items down the menu.

Note The InsertMenu function has been superseded by the InsertMenuItem function. You can still use InsertMenu, however, if you do not need any of the extended features of InsertMenuItem.

http://msdn.microsoft.com/en-us/library/ms647987%28VS.85%29.aspx

5、SetMenuItemBitmaps Function

Associates the specified bitmap with a menu item. Whether the menu item is selected or clear, the system displays the appropriate bitmap next to the menu item.

http://msdn.microsoft.com/en-us/library/ms647998%28VS.85%29.aspx

6、GetCurrentDirectory Function

Retrieves the current directory for the current process.

SetCurrentDirectory

7、lstrcpyn Function

Copies a specified number of characters from a source string into a buffer.

Warning Do not use. Consider using StringCchCopy instead. See Remarks.

8、CStdioFile::ReadString

Reads text data into a buffer, up to a limit of nMax–1 characters, from the file associated with the CStdioFile object.

Reading is stopped by the first newline character. If, in that case, fewer than nMax–1 characters have been read, a newline character is stored in the buffer. A null character ('\0') is appended in either case.

http://msdn.microsoft.com/en-us/library/x5t0zfyf%28VS.80%29.aspx

9、FindFirstFile Function

Searches a directory for a file or subdirectory with a name that matches a specific name (or partial name if wildcards are used).

To specify additional attributes to use in a search, use the FindFirstFileEx function.

To perform this operation as a transacted operation, use the FindFirstFileTransacted function.

http://msdn.microsoft.com/en-us/library/aa364418%28VS.85%29.aspx

10、SHGetPathFromIDList Function

Converts an item identifier list to a file system path.

http://msdn.microsoft.com/en-us/library/bb762194%28VS.85%29.aspx

11、GetTopWindow Function

Examines the Z order of the child windows associated with the specified parent window and retrieves a handle to the child window at the top of the Z order.

http://msdn.microsoft.com/en-us/library/ms633514%28VS.85%29.aspx

12、HIWORD Macro

Retrieves the high-order word from the specified 32-bit value.

http://msdn.microsoft.com/en-us/library/ms632657%28VS.85%29.aspx

13、SHGetMalloc

Retrieves a pointer to the shell's IMalloc interface

12、FindNextFile

Continues a file search from a previous call to the FindFirstFile or FindFirstFileEx function.

http://msdn.microsoft.com/en-us/library/aa364428%28VS.85%29.aspx

 

代码
     
     
1 void CShellExtentionToVirC::RecursiveObtainDirFiles(WCHAR *lpPath) 2 { 3 WCHAR szFind[MAX_PATH]; 4 WCHAR szFile[MAX_PATH]; 5 WIN32_FIND_DATA FindFileData; 6 7 wcscpy(szFind, lpPath); 8 wcscat(szFind, TEXT("\\*.*")); 9 10 HANDLE hFind=::FindFirstFile((LPCWSTR)szFind, &FindFileData); 11  if (INVALID_HANDLE_VALUE == hFind) 12 return; 13 14 while(TRUE) 15 { 16 if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 17 { 18 if(FindFileData.cFileName[0] != L'.') 19 { 20 wcscpy(szFile, lpPath); 21 wcscat(szFile, TEXT("\\")); 22 wcscat(szFile, FindFileData.cFileName); 23 RecursiveObtainDirFiles(szFile); 24 } 25 } 26 else 27 { 28 std::wcout << FindFileData.cFileName << std::endl; 29 } 30 if (!FindNextFile(hFind, &FindFileData)) 31 break; 32 } 33 FindClose(hFind); 34 } 35 36 37 HRESULT CShellExtentionToVirC::InvokeCommand(LPCMINVOKECOMMANDINFO pInfo) 38 { 39 // If lpVerb really points to a string, ignore this function call and bail out. 40 if ( 0 != HIWORD( pInfo->lpVerb )) 41 return E_INVALIDARG; 42 43 // Check that lpVerb is one of our commands (0 or 1) 44 45 switch ( LOWORD( pInfo->lpVerb )) 46 { 47 case 0: 48 case 1: 49 { 50 TCHAR szMsg[MAX_PATH + 32]; 51 wsprintf( szMsg, _T("Only a test")); 52 MessageBox( pInfo->hwnd, szMsg, _T("VirCS"),MB_ICONINFORMATION ); 53 54 strCurrentDirectory = L"C:\\Intel\\Logs"; 55 RecursiveObtainDirFiles(const_cast<WCHAR*>(strCurrentDirectory.data())); 56 return S_OK; 57 } 58 break; 59 60 default: 61 return E_INVALIDARG; 62 break; 63 } 64 }

参考

[1] http://www.codeproject.com/KB/shell/shellextguideindex.aspx

关 于Shell Extension,CodeProject讲解了更多,如drag and drop handler(用右键拖拽时显示的菜单),property sheet handler(在属性页中显示的菜单),icon handler(不同类型文件图标不同)等。

[2] 相关文档下载地址

http://download.csdn.net/source/2878021

[3] http://www.codeproject.com/KB/shell/wildcardselect.aspx

[4] http://msdn.microsoft.com/en-us/library/bb776426%28VS.85%29.aspx

[5] http://blog.csdn.net/luckyboy101/archive/2009/11/25/4866408.aspx

[6] http://www.programbbs.com/

[7] http://www.cnblogs.com/MaxWoods/archive/2010/06/23/1764036.html

[8] http://www.cnblogs.com/MaxWoods/archive/2010/06/23/1764034.html

[9] http://www.cnblogs.com/lemony/archive/2007/04/16/715833.html

[10] http://www.cnblogs.com/lemony/archive/2007/04/16/715833.html

[11] Dino Esposito's great book Visual C++ Windows Shell Programming (ISBN 1861001843)

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Windows Shell扩展编程完全指南 第一节 - 一步步教你如何编写Shell扩展 第二节 - 如何编写一次操作多个文件对象的Shell扩展 第三节-如何编写为文件对象弹出提示信息框的Shell扩展 第四节 - 如何编写提供定制拖放功能的Shell扩展 第五节-如何编写添加属性页到文件属性对话框中的Shell扩展 第六节-如何编写定制”发送到”菜单Shell扩展 第七节-如何编写自画上下文菜单项的Shell扩展, 以及如何使上下文菜单扩展响应文件夹窗口背景上的鼠标右击事件 第八节-如何使用信息栏扩展添加定制的信息栏到资源浏览器详细资料列表中 Windows Shell扩展编程完全指南 目录与资料简介 第一节 - 一步步教你如何编写Shell扩展 简要概述了Shell扩展及如何对之进行调试. 所附的例子演示了如何为文本文件对象添加上下文菜单项。 -------------------------------------------------------------------------------- 第二节 - 如何编写一次操作多个文件对象的Shell扩展 示范了如何编写一次操作多个被选择文件的上下文菜单扩展。所附的例子为DLL文件的上下文菜单项添加”注册”和”注销”两项以方便DLL服务器的注册操作. -------------------------------------------------------------------------------- 第三节-如何编写为文件对象弹出提示信息框的Shell扩展 示范了如何使用QueryInfo 扩展为文本文件对象提供提示信息框,同时还解释了如何在Shell扩展中使用 MFC. -------------------------------------------------------------------------------- 第四节 - 如何编写提供定制拖放功能的Shell扩展 示范了如何添加菜单项到 用户用右键拖放文件对象时弹出的上下文菜单. 所附的例子为文件对象生成硬链接(hard link). (注: 该扩展只在Windows 2000下起作用, 但你可以在以前版本的Windows中编译并运行该扩展(具体使用请见文章内容) -------------------------------------------------------------------------------- 第六节-如何编写定制”发送到”菜单Shell扩展 讨论了如何使用放置目标处理器扩展添加菜单项到”发送到”菜单. 所附的例子实现了将文件发送到任一文件夹的功能。 第五节-如何编写添加属性页到文件属性对话框中的Shell扩展 示范了如何添加新定制的属性页到文件属性对话框中. 所附的例子添加一个定制的属性页,使用它你可以编辑文件对象的创建,修改和最后访问时间. -------------------------------------------------------------------------------- 第七节-如何编写自画上下文菜单项的Shell扩展, 以及如何使上下文菜单扩展响应文件夹窗口背景上的鼠标右击事件 解决了读者提出的两个问题: 自画菜单项和文件夹窗口背景上的上下文菜单. 所附的例子包含两个扩展: 位图预览 (如上图) 在上下文菜单上显示BMP文件的缩略图;另一个扩展添加菜单项到文件夹窗口背景上下文菜单. -------------------------------------------------------------------------------- 第八节-如何使用信息栏扩展添加定制的信息栏到资源浏览器详细资料列表中 示范了如何添加定制信息栏到Windows 2000资源浏览器的详细信息列表. 所附的例子添加若干个信息栏以显示MP3文件的资料标签数据 (改扩展仅用于Windows 2000.) -------------------------------------------------------------------------------- 第九节-如何编写定制文件类型显示图标的Shell扩展

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值