用Shell擴充套件實現原始碼統計程式
在 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.新建一個VC工程,選定ATL COM AppWizard型別,工程起名為SrcCount,進入下一步;
2.選擇服務型別為DLL(預設選項)即可,這裡不需要MFC支援(若加入MFC支援的話,編寫程式碼時會方便些,但程式失去ATL短小精悍的特點了,熊掌與魚不可兼得:)),進入下一步;
3.現在會顯示工程的配置資訊,我們按確定按鈕後就建立一個ATL COM元件了。
4.我們現在加入一個元件物件,在工程的快捷選單上選擇New ATL Object…,在隨後的對話方塊中的種類中選擇Simple Object,單擊下一步,在“Short Name”中填寫CountLines,Attributes屬性頁中按預設選項,單擊確定按鈕。我們可以在VC的工作區裡看到已新增一個介面ICountLines。
5.為該介面新增方法,在介面的快捷選單上按右鍵,選擇Add method…,方法名為GetFileLines,它的引數分別為:[in]BSTR *pFilePath,[out]int *lines。它的作用是統計原始碼檔案的行數。下面是程式碼的主要實現部分:
01.
//////////////////////////////////////////////////////////////////
02.
// 作用:獲取原始檔的程式碼行數
03.
// 引數:1. pFilePath :輸入引數,指定原始檔的路徑;
04.
// 2. lines:輸出引數,獲得原始檔的程式碼行數。
05.
STDMETHODIMP CCountLines::GetFileLines(BSTR *pFilePath,
int
*lines)
06.
{
07.
// 存放程式碼的總行數
08.
int
totalLine = 0; ?
09.
// 開啟檔案
10.
HANDLE
hFile = CreateFile((
TCHAR
*)pFilePath, ?
11.
GENERIC_READ,
12.
FILE_SHARE_READ,
13.
NULL,
14.
OPEN_EXISTING,
15.
FILE_ATTRIBUTE_NORMAL,
16.
NULL);
17.
if
((
HANDLE
)-1 == hFile)
18.
{
19.
*lines = -1;
20.
return
S_FALSE;
21.
}
22.
// 開闢緩衝區存放檔案內容
23.
DWORD
dwFileSize = GetFileSize(hFile, NULL);
24.
BYTE
*lpBuffer =
new
BYTE
[dwFileSize];
25.
memset
(lpBuffer, 0, dwFileSize); ?
26.
DWORD
dwRead = 0;
27.
BOOL
bReadFile = ReadFile(hFile, lpBuffer, dwFileSize, &dwRead, NULL);
28.
assert
(bReadFile && dwRead == dwFileSize); ?
29.
// 我們這裡僅用一個簡單的統計規則,即以換行符(‘\n’)為一行程式碼結束的標記
30.
for
(unsigned i = 0; i < dwFileSize; i++)
31.
{
32.
if
(lpBuffer[i] ==
''
\n
''
)
33.
{
34.
totalLine++;
35.
}
36.
}
37.
// 釋放緩衝區
38.
delete
[]lpBuffer;
39.
CloseHandle(hFile);
40.
// 儲存程式碼行數
41.
*lines = totalLine + 1; ?
42.
return
S_OK;
43.
}
6.繼續新增方法GetFolderLines,它將根據遞迴演算法對資料夾裡的每個檔案進行程式碼統計,這裡就不具體寫出了,請參看原始碼。
7.在CCountLines的基類中新增IShellExtInit、IContextMenu。
8.當瀏覽器explorer.exe載入我們的程式段時,將呼叫IShellExtInit 介面初始化選單,然後呼叫介面IContexMenu處理右鍵選單,所以我們將在DLL元件中暴露以上介面。這隻需要在BEGIN_COM_MAP()與END_COM_MAP()巨集中加入介面即可。
9.Windows視窗初始化快捷選單時呼叫IShellExtInit介面的Initialize ()方法,函式原型如下:
1.
HRESULT
Initialize(LPCITEMIDLIST pidlFolder,LPDATAOBJECT lpdobj,
HKEY
hkeyProgID );
我們將在這個函式裡進行必要的初始化動作,例如儲存檔名的完整路徑,儲存登錄檔的鍵值等。
10.瀏覽器呼叫IContexMenu介面進行命令的解釋執行,這是我們進行原始碼統計的主要部分,我們將呼叫以上的演算法對所選定的資料夾按照約定的規則進行程式碼統計。這個介面主要有以下三個方法需要實現:
01.
// 在視窗的狀態列上顯示命令說明,這裡值得注意的是,我們需要對ASCII碼和UNICODE碼進行處理,
02.
// 以適應不同系統。
03.
04.
HRESULT
GetCommandString(
05.
UINT
idCmd,
06.
UINT
uFlags,
07.
UINT
*pwReserved,
08.
LPSTR
pszName,
09.
UINT
cchMax
10.
);
11.
12.
// 執行選單明命令,在此可以實現具體的功能。
13.
HRESULT
InvokeCommand(
14.
LPCMINVOKECOMMANDINFO pici
15.
);
16.
17.
// 在這裡增加快捷選單
18.
HRESULT
QueryContextMenu(
19.
HMENU
hmenu,
20.
UINT
indexMenu,
21.
UINT
idCmdFirst,
22.
UINT
idCmdLast,
23.
UINT
uFlags
24.
);
這裡僅舉例 InvokeCommand()的實現,其他請看原始碼。
01.
///////////////////////////////////////////////////////////////////
02.
// 作用:執行快捷選單命令
03.
// 引數:1. pici:包含命令資訊的結構體
04.
HRESULT
CCountLines::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
05.
{
06.
BOOL
bEx = FALSE;
07.
BOOL
bUnicode = FALSE;?
08.
09.
if
(pici->cbSize =
sizeof
(CMINVOKECOMMANDINFOEX))
10.
{
11.
bEx = TRUE;
12.
if
((pici->fMask & CMIC_MASK_UNICODE))
13.
{
14.
bUnicode = TRUE;
15.
}
16.
}
17.
// lpVerb引數有兩種標識:如高位字非0,則為命令字串,否則低位提供了快捷選單的偏移值。
18.
if
(!bUnicode && HIWORD(pici->lpVerb))
19.
{
20.
if
(StrCmpIA(pici->lpVerb,
"Stat."
))
21.
{
22.
23.
return
E_FAIL;
24.
}
25.
}
26.
27.
else
if
(bUnicode && HIWORD(((CMINVOKECOMMANDINFOEX *) pici)->lpVerbW))
28.
{
29.
if
(StrCmpIW(((CMINVOKECOMMANDINFOEX *)pici)->lpVerbW, L
"Stat."
))
30.
{
31.
return
E_FAIL;
32.
}
33.
}
34.
else
if
(LOWORD(pici->lpVerb) != IDM_SRC_COUNT)
35.
{
36.
37.
return
E_FAIL;
38.
}
39.
else
40.
{
41.
assert
(0 == HIWORD(pici->lpVerb));
42.
int
lines = 0;
43.
TCHAR
szTitle[MAX_PATH] = {0};
44.
TCHAR
szMsg[MAX_PATH] = {0};
45.
TCHAR
szFormat[MAX_PATH] = {0};
46.
memset
(szMsg, 0, MAX_PATH);
47.
//儲存當前游標並重設為等待形狀
48.
HCURSOR
hOldCursor = GetCursor();??
49.
HCURSOR
hNewCursor = LoadCursor(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDC_COUNT_WAIT));
50.
assert
(hNewCursor);
51.
SetCursor(hNewCursor);?
52.
TCHAR
szTemp[MAX_PATH] = {0};
53.
LoadString(_Module.GetModuleInstance(), IDS_TOTAL_LINES, szFormat, MAX_PATH);
54.
55.
if
(SUCCEEDED(GetFolderLines((BSTR *)&m_pszPath, &lines)))
56.
{
57.
wsprintf(szMsg, szFormat, (
LPTSTR
)m_pszPath, lines);
58.
}
59.
60.
// 恢復預設游標形狀
61.
SetCursor(hOldCursor);
62.
63.
// 顯示統計程式碼資訊
64.
LoadString(_Module.GetModuleInstance(), IDS_TITLE, szTitle, MAX_PATH);
65.
MessageBox(pici->hwnd, szMsg, szTitle, MB_OK | MB_ICONINFORMATION);
66.
}
67.
return
S_OK;
68.
}
四、其它
本程式是程式內服務程式,執行regsvr32進行註冊(注:在VC編譯器中,COM元件在編譯時會自動呼叫regsvr32 進行註冊,請看工程配置檔案),例如,該元件已COPY至C:\WinNT\System32下,我們將輸入如下命令列註冊:
圖三 示例程式碼執行效果圖三
因為是對資料夾統計,所以還需在
1.
HKEY_CLASSES_ROOT\Directory\Shellex\ContextMenuHandlers\
下新建一項,命名為SrcCount,它的預設鍵值是元件的GUID,這裡為:
1.
{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.
相關文章
- 用SQL Server寫指令碼和程式設計實現SSIS包的擴充套件SQLServer指令碼程式設計套件
- Shell擴充套件程式設計實現Windows2000桌面圖示透明套件程式設計Windows
- shell擴充套件——免互動指令碼套件指令碼
- shell中擴充套件命令套件
- 怎樣用 Bash 程式設計:邏輯操作符和 shell 擴充套件程式設計套件
- GNOME Shell Extension常用擴充套件套件
- 擴充套件JAAS,XMLPolicyFile實現套件XML
- CheckBoxList擴充套件方法程式碼套件
- linux下php實現C/C++擴充套件程式設計LinuxPHPC++套件程式設計
- PostgreSQL 原始碼解讀(216)- 實現簡單的擴充套件函式SQL原始碼套件函式
- 聊聊Dubbo(五):核心原始碼-SPI擴充套件原始碼套件
- 原生js實現的物件複製和擴充套件程式碼例項JS物件套件
- CONNECT BY 擴充套件用法,實現獲取bom級聯擴充套件數量套件
- “Simple Dock” GNOME Shell擴充套件乾坤大挪移,鍾愛應用桌面展現套件
- 乾貨|Linux程式函式棧列印工具gstack原始碼解讀、運用及擴充套件程式設計Linux函式原始碼套件程式設計
- COLA的擴充套件性使用和原始碼研究套件原始碼
- 原始碼安裝memcached和php memcache擴充套件原始碼PHP套件
- ?用Chrome擴充套件管理器, 管理你的擴充套件Chrome套件
- [外掛擴充套件]通用網站統計套件網站
- kotlin 擴充套件(擴充套件函式和擴充套件屬性)Kotlin套件函式
- Minecraft中ScoreBoard的底層實現與擴充套件應用Raft套件
- 實用的可選項(Optional)擴充套件套件
- Mac Safari上有趣實用的擴充套件Mac套件
- 基於PHP擴充套件的WAF實現PHP套件
- dubbo是如何實現可擴充套件的?套件
- JWT 擴充套件具體實現詳解JWT套件
- 單體系統如何實現動態演進擴充套件套件
- 小程式開發實用技巧——擴充套件 Page 頁面物件套件物件
- 使用高階函式實現類的擴充套件設計函式套件
- 聊聊Dubbo – Dubbo可擴充套件機制原始碼解析套件原始碼
- [VS Code擴充套件]寫一個程式碼片段管理外掛(二):功能實現套件
- windows系統磁碟擴容/擴充套件Windows套件
- 不容錯過這十款 GNOME Shell 擴充套件套件
- Oracle優化案例-擴充套件統計資訊(十四)Oracle優化套件
- 寫擴充套件性好的程式碼:函式套件函式
- 微軟推出必應程式碼搜尋擴充套件微軟套件
- chrome擴充套件程式開發Chrome套件
- 編寫可擴充套件程式套件