做过Oracle数据库sql优化的朋友一定都知道trc文件,该文件中记录了此次会话sql执行情况的完整信息。然而该文件内容的可读性并不好,每次使用都需要利用Oracle中提供的tkprof工具在命令行中进行格式转换,如此给本来就很费脑子的sql调试带来了很多不必要的重复工作,时间久了工作效率可想而知。所以为了提高工作效率,并让sql调试过程更具乐趣,笔者利用Windows Shell在.trc文件类型的右键菜单中添加了自动利用tkprof.exe进行格式转换的菜单项,对指定.trc文件单击该菜单项后可直接弹出进行了格式转换后的sql跟踪内容。下面我就详细介绍下该功能的实现原理。
Windows Shell给用户提供了用于控制文件显示和行为的一组接口:IShellExtInit, IContextMenu, IPersistFile, IExtractIcon, IQueryInfo,其中,
IShellExtInit接口用于一次有多个选择文件时初始化的处理,其C#版本的接口定义如下(如无特别说明以下均为C#代码):
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute( " 000214e8-0000-0000-c000-000000000046 " )]
public interface IShellExtInit
{
[PreserveSig()]
int Initialize(IntPtr pidlFolder, IntPtr lpdobj, uint /* HKEY */ hKeyProgID);
}
IPersistFile接口用于一次仅有一个选择文件时初始化的处理,其接口定义如下:
1 [ComImport(), ComVisible( true ), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute( " 0000010b-0000-0000-C000-000000000046 " )]
2
3 public interface IPersistFile
4
5 {
6
7 [PreserveSig]
8
9 uint GetClassID( out Guid pClassID);
10
11 [PreserveSig]
12
13 uint IsDirty();
14
15 [PreserveSig]
16
17 uint Load([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, [In] uint dwMode);
18
19 [PreserveSig]
20
21 uint Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, [In] bool fRemember);
22
23 [PreserveSig]
24
25 uint SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName);
26
27 [PreserveSig]
28
29 uint GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName);
30
31 }
32
33
IContextMenu接口用于对文件自定义右键菜单项的控制,其接口定义如下:
1 [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute( " 000214e4-0000-0000-c000-000000000046 " )]
2
3 public interface IContextMenu
4
5 {
6
7 [PreserveSig()]
8
9 int QueryContextMenu(HMenu hmenu, uint iMenu, uint idCmdFirst, uint idCmdLast, CMF uFlags);
10
11 [PreserveSig()]
12
13 void InvokeCommand(IntPtr pici);
14
15 [PreserveSig()]
16
17 void GetCommandString( uint idcmd, GCS uflags, uint reserved, IntPtr commandstring, int cchMax);
18
19 }
20
21
IExtractIcon接口用于对文件图标的控制,其定义如下:
1 [ComVisible( true ), ComImport, Guid( " 000214eb-0000-0000-c000-000000000046 " ), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
2
3 public interface IExtractIcon
4
5 {
6
7 [PreserveSig]
8
9 int GetIconLocation([In] ExtractIconOptions uFlags,
10
11 [In] IntPtr szIconFile,
12
13 [In] uint cchMax,
14
15 [Out] out int piIndex,
16
17 [Out] out ExtractIconFlags pwFlags);
18
19
20
21 [PreserveSig]
22
23 int Extract([In, MarshalAs(UnmanagedType.LPWStr)] string pszFile,
24
25 uint nIconIndex,
26
27 [Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (IconMarshaler))] out Icon phiconLarge,
28
29 [Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (IconMarshaler))] out Icon phiconSmall,
30
31 [In] uint nIconSize);
32
33 }
34
IQueryInfo接口用于对当鼠标移至文件图标上方时弹出tip信息的控制,其定义如下:
1 [ComImport(), ComVisible( true ), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute( " 00021500-0000-0000-c000-000000000046 " )]
2
3 public interface IQueryInfo
4
5 {
6
7 [PreserveSig]
8
9 uint GetInfoTip( uint dwFlags, out IntPtr pszInfoTip);
10
11
12
13 [PreserveSig]
14
15 uint GetInfoFlags( out uint dwFlags);
16
17 }
18
用户自定义的扩展类需要实现以上这些接口,关键代码如下:
int IContextMenu.QueryContextMenu(HMenu hMenu, uint iMenu, uint idCmdFirst, uint idCmdLast, CMF uFlags)
{
int id = 0 ;
if ((uFlags & (CMF.CMF_VERBSONLY | CMF.CMF_DEFAULTONLY | CMF.CMF_NOVERBS)) == 0 ||
(uFlags & CMF.CMF_EXPLORE) != 0 )
{
// 创建子菜单
HMenu submenu = ShellLib.Helpers.CreatePopupMenu();
Helpers.AppendMenu(hMenu, MFMENU.MF_STRING, new IntPtr(idCmdFirst + id ++ ), " 可读格式 " );
// 为菜单增加图标
Bitmap bpCopy = Resource1.copy;
Helpers.SetMenuItemBitmaps(hMenu, 0 , MFMENU.MF_BYPOSITION, bpCopy.GetHbitmap(), bpCopy.GetHbitmap());
}
return id;
}
1 void IContextMenu.GetCommandString( uint idcmd, GCS uflags, uint reserved, IntPtr commandstring, int cchMax)
2
3 {
4
5 string tip = "" ;
6
7
8
9 switch (uflags)
10
11 {
12
13 case GCS.VERB:
14
15 break ;
16
17 case GCS.HELPTEXTW:
18
19 tip = " 把选中的TRC转换为可读格式 " ;
20
21 break ;
22
23
24
25 if ( ! string .IsNullOrEmpty(tip))
26
27 {
28
29 byte [] data = new byte [cchMax * 2 ];
30
31 Encoding.Unicode.GetBytes(tip, 0 , tip.Length, data, 0 );
32
33 Marshal.Copy(data, 0 , commandstring, data.Length);
34
35 }
36
37 break ;
38
39 }
40
41 }
42
1 void IContextMenu.InvokeCommand(IntPtr pici)
2
3 {
4
5 INVOKECOMMANDINFO ici = (INVOKECOMMANDINFO)Marshal.PtrToStructure(pici, typeof (ShellLib.INVOKECOMMANDINFO));
6
7 StringBuilder sb = new StringBuilder( 1024 );
8
9 StringBuilder sbAll = new StringBuilder();
10
11 uint nselected;
12
13
14
15 switch (ici.verb)
16
17 {
18
19 case 0 :
20
21 // 复制文件名
22
23 nselected = Helpers.DragQueryFile(m_hDrop, 0xffffffff , null , 0 );
24
25 for ( uint i = 0 ; i < nselected; i ++ )
26
27 {
28
29 ShellLib.Helpers.DragQueryFile(m_hDrop, i, sb, sb.Capacity + 1 );
30
31 string trctmpfile = System.IO.Path.GetTempFileName();
32
33 string txttmpfile = System.IO.Path.GetTempFileName();
34
35 File.AppendAllText(trctmpfile, sb.ToString());
36
37 Process proc = new Process();
38
39 proc.StartInfo.FileName = @" tkprof.exe " ;
40
41 proc.StartInfo.Arguments = trctmpfile + " " + txttmpfile;
42
43 proc.Start();
44
45 while ( ! proc.HasExited)
46
47 {
48
49 Application.DoEvents();
50
51 }
52
53 proc.StartInfo.FileName = " NOTEPAD.EXE " ;
54
55 proc.StartInfo.Arguments = txttmpfile;
56
57 proc.Start();
58
59 }
60
61 break ;
62
63 default :
64
65 break ;
66
67 }
68
69 }
70
(注:其它代码请见附件。)
也许有些细心好学的朋友要问了,Windows Shell是如何与用户自定义的Shell扩展类进行交互的呢?下面就简单介绍下Windows Shell调用用户自定义Shell扩展类的工作原理。
在Windows中Shell要成功调用用户自定义的Shell扩展类需要三大要素:Explorer.exe进程、注册表、自定义扩展类,其中这种Shell扩展机制的核心是注册表。在注册表的HKEY_CLASSES_ROOT中管理了两种类型的子项:一是操作系统中所有文件的扩展名,二是操作系统中的所有类型。其中"扩展名"子项主要维护了该扩展名所对应的类型以及打开具有该扩展名的文件所对应的应用程序的配置;"类型"子项主要维护了具有该类型的扩展名所对应的文件的显示和行为的配置。所以,要想实现Shell与扩展类的交互,则必须在HKEY_CLASSES_ROOT下为指定扩展名创建相应类型,并将扩展类com的guid信息配置到该类型下的各个Handler子项中。如此,当用户在用资源管理器进行文件浏览或操作时,Explorer进程首先将在注册表的HKEY_CLASSES_ROOT项下查找出该文件的扩展名配置项,然后通过在该项中配置的扩展名所属类型,在同级项中找出该类型所对应的注册表项,最后再根据"类型"项中与用户当前操作相对应的项的值(com的guid)最终找到相应的com组件,并以如上标准接口进行方法调用。
所以根据以上原理,在用户自定义的扩展类中还需加入RegisterServer和UnregisterServer两方法,以实现在使用RegAsm.exe工具将扩展类注册/卸载同时先后实现:1、将com组件的guid注册到Shell扩展的组件池中(或卸载);2、注册/卸载文件扩展名及其类型配置信息。代码如下:
1 [System.Runtime.InteropServices.ComRegisterFunctionAttribute()]
2 static void RegisterServer(String str1)
3 {
4 try
5 {
6 // 注册 DLL
7 RegistryKey root;
8 RegistryKey rk;
9 root = Registry.LocalMachine;
10 rk = root.OpenSubKey( " Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved " , true );
11 rk.SetValue(GUID, KEYNAME);
12 rk.Close();
13 root.Close();
14
15 // 注册文件
16 RegTRC();
17 }
18 catch (Exception ex){
19 }
20 }
21
22 private static void RegTRC()
23 {
24 RegistryKey root;
25 RegistryKey rk;
26
27 root = Registry.ClassesRoot;
28 rk = root.OpenSubKey( " .trc " );
29 if (rk == null )
30 {
31 rk = root.CreateSubKey( " .trc " );
32 }
33 string txtclass = ( string )rk.GetValue( "" );
34 if ( string .IsNullOrEmpty(txtclass))
35 {
36 txtclass = " TXT " ;
37 rk.SetValue( "" , txtclass);
38
39 }
40
41
42 rk.Close();
43
44 rk = root.CreateSubKey(txtclass + " \\shellex\\ContextMenuHandlers\\ " + KEYNAME);
45 rk.SetValue( "" , GUID);
46 rk.Close();
47
48 rk = root.CreateSubKey(txtclass + " \\shellex\\IconHandler " );
49 rk.SetValue( "" , GUID);
50 rk.Close();
51
52 rk = root.CreateSubKey(txtclass + " \\shellex\\{00021500-0000-0000-C000-000000000046} " );
53 rk.SetValue( "" , GUID);
54 rk.Close();
55 }
56
1 [System.Runtime.InteropServices.ComUnregisterFunctionAttribute()]
2 static void UnregisterServer(String str1)
3 {
4 try
5 {
6 // 注销动态库
7 RegistryKey root;
8 RegistryKey rk;
9 root = Registry.LocalMachine;
10 rk = root.OpenSubKey( " Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved " , true );
11 rk.DeleteValue(GUID);
12 rk.Close();
13 root.Close();
14
15 // 注销文件
16 UnRegTRC();
17 }
18 catch
19 {
20 }
21 }
22
23 private static void UnRegTRC()
24 {
25 RegistryKey root;
26 RegistryKey rk;
27
28 root = Registry.ClassesRoot;
29 rk = root.OpenSubKey( " .trc " );
30 rk.Close();
31 string txtclass = ( string )rk.GetValue( "" );
32 if ( ! string .IsNullOrEmpty(txtclass))
33 {
34 root.DeleteSubKey(txtclass + " \\shellex\\ContextMenuHandlers\\ " + KEYNAME);
35 root.DeleteSubKey(txtclass + " \\shellex\\IconHandler " );
36 root.DeleteSubKey(txtclass + " \\shellex\\{00021500-0000-0000-C000-000000000046} " );
37 }
38 }
39
最后,利用在命令行中使用"regasm MyContextMenu.dll /CodeBase"命令,将dll注册到Shell中即可实现如题功能。哈哈,over!