用Shell扩展实现源代码统计程序
作者/king_dxs
下载源代码
一、前言
在 Windows 的资源管理器窗口中,我们见过 WinZIP,WinRAR 等软件能在文件或文件夹的默认快捷菜单中添加几个菜单项,它可以使用户无须进入软件内部而直接在视窗中进行压缩/解压操作,十分方便用户操作,这无疑是一 个较好的应用模型,它就是我们所说的Shell扩展技术。本文将以一个普通的源代码统计程序为例来说明怎样实现Shell扩展技术。下面是程序的运行效果 图:
图一 示例代码运行效果图一
图二 示例代码运行效果图二
二、实现原理
为了在Windows的任何视窗中扩展文件或文件夹的默认菜单,我们必须使Windows在显示快捷菜单加载我们的程序段,一般我们利用COM组件来达到 这个目的。COM组件分为三种:进程内服务程序,本地服务程序,以及远程服务程序。要想让explorer加载并执行我们的代码,当然得使用进程内服务程 序,它的表现形式是DLL, DLL在加载后被映射到可执行程序的虚拟地址空间,我们向explorer提供一些接口,explorer将在显示快捷菜单时调用它们时,我们可以在那些 接口中做一些我们想做的事,如添加快捷菜单,实现菜单项功能等等,从而实现Shell扩展了。?
至于源代码统计,则不难实现。这里我以C/C++风格的源代码为例,并应用一种最简单的统计规则,当统计文件时,我们将代码内容读入缓存,判断每一个字符 是否为换行符(\n),若是,计数加1。当然我们是对文件夹进行统计更有意义,所以我们可以使用递归的方法遍历文件夹内所有文件,找出有效文件(这里我仅 统计C/C++程序,所以只处理后缀名为.C、.CPP、.H 的文件),根据前面的方法一一统计即可求出文件夹内所有代码的总行数。
三、实现过程
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// 作用:获取源文件的代码行数
// 参数:1. pFilePath :输入参数,指定源文件的路径;
// 2. lines:输出参数,获得源文件的代码行数。
STDMETHODIMP CCountLines::GetFileLines(BSTR *pFilePath, int *lines)
{
// 存放代码的总行数
int totalLine = 0; ?
// 打开文件
HANDLE hFile = CreateFile((TCHAR *)pFilePath, ?
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if ((HANDLE)-1 == hFile)
{
*lines = -1;
return S_FALSE;
}
// 开辟缓冲区存放文件内容
DWORD dwFileSize = GetFileSize(hFile, NULL);
BYTE *lpBuffer = new BYTE[dwFileSize];
memset(lpBuffer, 0, dwFileSize); ?
DWORD dwRead = 0;
BOOL bReadFile = ReadFile(hFile, lpBuffer, dwFileSize, &dwRead, NULL);
assert(bReadFile && dwRead == dwFileSize); ?
// 我们这里仅用一个简单的统计规则,即以换行符(‘\n’)为一行代码结束的标记
for (unsigned i = 0; i < dwFileSize; i++)
{
if (lpBuffer[i] == ''\n'')
{
totalLine++;
}
}
// 释放缓冲区
delete []lpBuffer;
CloseHandle(hFile);
// 保存代码行数
*lines = totalLine + 1; ?
return S_OK;
}
HRESULT Initialize(LPCITEMIDLIST pidlFolder,LPDATAOBJECT lpdobj, HKEY hkeyProgID );我们将在这个函数里进行必要的初始化动作,例如保存文件名的完整路径,保存注册表的键值等。
// 在视窗的状态栏上显示命令说明,这里值得注意的是,我们需要对ASCII码和UNICODE码进行处理,
// 以适应不同系统。
HRESULT GetCommandString(
UINT idCmd,
UINT uFlags,
UINT *pwReserved,
LPSTR pszName,
UINT cchMax
);
// 执行菜单明命令,在此可以实现具体的功能。
HRESULT InvokeCommand(
LPCMINVOKECOMMANDINFO pici
);
// 在这里增加快捷菜单
HRESULT QueryContextMenu(
HMENU hmenu,
UINT indexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags
);
这里仅举例 InvokeCommand()的实现,其他请看源代码。
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// 作用:执行快捷菜单命令
// 参数:1. pici:包含命令信息的结构体
HRESULT CCountLines::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
BOOL bEx = FALSE;
BOOL bUnicode = FALSE;?
if (pici->cbSize = sizeof(CMINVOKECOMMANDINFOEX))
{
bEx = TRUE;
if ((pici->fMask & CMIC_MASK_UNICODE))
{
bUnicode = TRUE;
}
}
// lpVerb参数有两种标识:如高位字非0,则为命令字串,否则低位提供了快捷菜单的偏移值。
if (!bUnicode && HIWORD(pici->lpVerb))
{
if(StrCmpIA(pici->lpVerb, "Stat."))
{
return E_FAIL;
}
}
else if (bUnicode && HIWORD(((CMINVOKECOMMANDINFOEX *) pici)->lpVerbW))
{
if(StrCmpIW(((CMINVOKECOMMANDINFOEX *)pici)->lpVerbW, L"Stat."))
{
return E_FAIL;
}
}
else if (LOWORD(pici->lpVerb) != IDM_SRC_COUNT)
{
return E_FAIL;
}
else
{
assert(0 == HIWORD(pici->lpVerb));
int lines = 0;
TCHAR szTitle[MAX_PATH] = {0};
TCHAR szMsg[MAX_PATH] = {0};
TCHAR szFormat[MAX_PATH] = {0};
memset(szMsg, 0, MAX_PATH);
//保存当前光标并重设为等待形状
HCURSOR hOldCursor = GetCursor();??
HCURSOR hNewCursor = LoadCursor(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDC_COUNT_WAIT));
assert(hNewCursor);
SetCursor(hNewCursor);?
TCHAR szTemp[MAX_PATH] = {0};
LoadString(_Module.GetModuleInstance(), IDS_TOTAL_LINES, szFormat, MAX_PATH);
if (SUCCEEDED(GetFolderLines((BSTR *)&m_pszPath, &lines)))
{
wsprintf(szMsg, szFormat, (LPTSTR)m_pszPath, lines);
}
// 恢复默认光标形状
SetCursor(hOldCursor);
// 显示统计代码信息
LoadString(_Module.GetModuleInstance(), IDS_TITLE, szTitle, MAX_PATH);
MessageBox(pici->hwnd, szMsg, szTitle, MB_OK | MB_ICONINFORMATION);
}
return S_OK;
}
四、其它
本程序是进程内服务程序,运行regsvr32进行注册(注:在VC编译器中,COM组件在编译时会自动调用regsvr32 进行注册,请看工程配置文件),例如,该组件已COPY至C:\WinNT\System32下,我们将输入如下命令行注册:
图三 示例代码运行效果图三
因为是对文件夹统计,所以还需在
HKEY_CLASSES_ROOT\Directory\Shellex\ContextMenuHandlers\
下新建一项,命名为SrcCount,它的默认键值是组件的GUID,这里为:
{548773BA-874E-4C02-9DC7-B7A096772C7D}
现在在资源管理器里对文件夹按快捷菜单,看到了吗,多出一菜单项了:源代码统计…,当我们单击该项时即可进行代码统计。
本程序主要是展示怎样使用Shell扩展,所以重点在于程序设计方法,并未对所有细节的地方做得尽善尽美。还有一些细节值得改进,如Shell扩展菜单的 美化效果(例如比较流行的软件WinZIP、WinRAR之类的界面效果,快捷菜单上绘出位图)还可以改进;此外,程序的功能可以进一步扩充,本文主要是 对C/C++源代码进行统计,我们可以扩展相关的统计规则,可以对汇编、JAVA、Delphi等各种语言的源代码进行统计,还可以用对话框的形式让用户 进行各种选择与设置统计规则等。有兴趣的朋友可以一试。
本程序虽在Windows XP、VC++6.0下编译,但可适用于Windows 9X/NT/2000/XP, 本文简单地简介了Shell扩展技术的实现方法,若有语焉不详的地方,请参考本文所附的源代码,或者发电子邮件给我,我们一起交流。
五、参考资料
1. MSDN, Microsoft Corp.