從任務(wù)通知區(qū)打開屏幕保護(hù)程序
發(fā)表時間:2024-06-20 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]作者:朱志強(qiáng)本文通過一個快速啟動屏幕保護(hù)程序的小程序SSLaunch,來介紹應(yīng)用程序如何向任務(wù)欄通知區(qū)加入圖標(biāo)、如何禁止多個Win32實例以及屏幕保護(hù)程序的有關(guān)內(nèi)容! SLaunch用C語言編寫,用Visual C++ 5.0編譯,是一個基于無模式對話框的程序,同時禁止多個實例,即一次只能有一...
作者:朱志強(qiáng)
本文通過一個快速啟動屏幕保護(hù)程序的小程序SSLaunch,來介紹應(yīng)用程序如何向任務(wù)欄通知區(qū)加入圖標(biāo)、如何禁止多個Win32實例以及屏幕保護(hù)程序的有關(guān)內(nèi)容。
SSLaunch用C語言編寫,用Visual C++ 5.0編譯,是一個基于無模式對話框的程序,同時禁止多個實例,即一次只能有一個實例運(yùn)行。任務(wù)欄通知區(qū)圖標(biāo)在對話框初始化時加入,對話框響應(yīng)程序定義的回調(diào)消息,當(dāng)鼠標(biāo)左鍵按下時,彈出一由屏幕保護(hù)程序名填充的上下文菜單。對話框關(guān)閉(即程序退出)時刪除任務(wù)欄通知區(qū)圖標(biāo)。如果讀者有興趣可以很容易地把它移植成基于 MFC 的程序。
1、任務(wù)欄通知區(qū)
Windows 95的任務(wù)欄中有一個通知區(qū), 應(yīng)用程序可以把一個圖標(biāo)放入其中,以表示操作狀態(tài),并可以有與之相關(guān)聯(lián)的工具用作說明控制。當(dāng)鼠標(biāo)出現(xiàn)在此圖標(biāo)的矩形邊界內(nèi)時,向相應(yīng)的應(yīng)用程序發(fā)送應(yīng)用程序定義的回調(diào)消息。應(yīng)用程序通過發(fā)送消息增加、修改、刪除任務(wù)欄圖標(biāo)。消息的發(fā)送通過調(diào)用函數(shù)Shell_NotifyIcon來完成,如果調(diào)用成功,則返回TRUE;否則,返回FALSE。Shell_NotifyIcon函數(shù)原形如下:
WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
DWORD dwMessage, // 消息標(biāo)識符
PNOTIFYICONDATA pnid // NOTIFYICONDATA 結(jié)構(gòu)
);
消息標(biāo)識符可以是 :
NIM_ADD 向任務(wù)欄通知區(qū)加入圖標(biāo)
NIM_DELETE 從任務(wù)欄通知區(qū)刪除圖標(biāo)
NIM_MODIFY 改變?nèi)蝿?wù)欄通知區(qū)圖標(biāo)
NOTIFYICONDATA 結(jié)構(gòu):
typedef struct _NOTIFYICONDATA {
DWORD cbSize;
HWND hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
HICON hIcon;
char szTip[64];
} NOTIFYICONDATA, *PNOTIFYICONDATA;
其中:
cbSize NOTIFYICONDATA 結(jié)構(gòu)大小
hWnd 接收回調(diào)消息窗口句柄
uID 任務(wù)欄通知區(qū)圖標(biāo)標(biāo)識
uFlags 指定該結(jié)構(gòu)中那些成員有效
uCallbackMessage 應(yīng)用程序定義的回調(diào)消息
hIcon 任務(wù)欄通知區(qū)圖標(biāo)句柄
szTip 任務(wù)欄通知區(qū)提示字符串
參數(shù)uFlags可以是下列值的組合:
NIF_ICON 任務(wù)欄通知區(qū)圖標(biāo)有效
NIF_MESSAGE 應(yīng)用程序定義的回調(diào)消息有效
NIF_TIP 任務(wù)欄通知區(qū)提示字符串有效
a.任務(wù)欄通知區(qū)圖標(biāo)的加入
BOOL SSLaunch_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
// Add an notification icon to the taskbar
NOTIFYCONDATA nid;
NOTIFYICONDATA nid;
nid.cbSize = sizeof(nid);
nid.hWnd = hwnd;
nid.uID = IDI_SSLAUNCH;
nid.uFlags = NIF_MESSAGE NIF_ICON NIF_TIP;
nid.uCallbackMessage = WM_SSLAUNCHICONNOTIFY;
nid.hIcon=LoadIcon(GetWindowInstance(hwnd),
KEINTRESOURCE(IDI_SSLAUNCH));
lstrcpyn(nid.szTip,g_szAppName,sizeof(nid.szTip) /sizeof(nid.szTip[0]));
return(Shell_NotifyIcon(NIM_ADD, &nid))
}
b.任務(wù)欄通知區(qū)圖標(biāo)的刪除
應(yīng)用程序退出時,應(yīng)該刪除任務(wù)通知區(qū)上相應(yīng)的圖標(biāo):
void SSLaunch_OnDestroy(HWND hwnd)
{
// Remove the notification icon from the taskbar
NOTIFYICONDATA nid;
nid.cbSize = sizeof(nid);
nid.hWnd = hwnd;
nid.uID = IDI_SSLAUNCH;
Shell_NotifyIcon(NIM_DELETE, &nid);
}
c.應(yīng)用程序定義回調(diào)消息的接收
若為任務(wù)欄通知區(qū)指定了回調(diào)消息,則系統(tǒng)會于鼠標(biāo)事件在此區(qū)域發(fā)生時
向應(yīng)用程序發(fā)送此消息,其中wParam是任務(wù)欄通知區(qū)圖標(biāo)標(biāo)識,lParam
是鼠標(biāo)事件發(fā)生后的鼠標(biāo)信息。
void SSLaunch_OnIconNotify(WPARAM wParam, LPARAM lParam)
{
UINT uID = (UINT)wParam;
UINT uMsg = (UINT)lParam;
if(uID == IDI_SSLAUNCH){
switch(uMsg){
case WM_LBUTTONDOWN :
//Do something
break;
case WM_LBUTTONUP :
//Do something
break;
default :
break;
}
}
}
2.禁止多個Win32實例
在討論禁止多個Win32實例之前,我們先討論一下WinMain函數(shù)。我們知道,任何一個基于GDI的Windows程序以WinMain函數(shù)作為入口被系統(tǒng)調(diào)用。在Win16中,hPrevInstance指向前一個實例的句柄,但在Win32中,每一個進(jìn)程都有一個獨(dú)立的4G地址空間,從0到2G屬于進(jìn)程私有,對其他進(jìn)程來說是不可見的。所以,在Win32中,hPrevInstance總是為NULL。
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // pointer to command line
int nCmdShow // show state of window
);
因而,在Win32下不能通過判斷hPrevInstance是否為NULL來判斷一個程序的另一個實例是否存在,要用其他的方法來判斷。
方法一
用FindWindow 函數(shù)查找指定窗口,如果成功,則返回要找的窗口的句柄,否則返回NULL,由此可判斷是否有程序的另一個實例存在。
下圖的代碼片段演示如何使用FindWindow函數(shù):
TCHAR szClassName[] = _TEXT("My Wnd Class");
TCHAR szWndName[] = _TEXT("My Wnd");
HWND hWnd = FindWindow(szClassName,szWndName);
if(hWnd){
MessageBox(NULL, _TEXT("Another Instance is already running."), _TEXT("Information"),
MB_OK MB_ICONINFORMATION);
}
需要注意的是,很可能程序的各個實例有不同的窗口名,如果象下面這樣調(diào)用FindWindow
HWND hWnd = FindWindow(szClassName,NULL);
則查找所有的窗口并匹配窗口類名,如果你能保證你的窗口類名是唯一的,那么你可以信賴FindWindow,否則,你需要用更好的方法。
方法二
通過在EXE之間共享數(shù)據(jù)段從而共享數(shù)據(jù)來判斷是否有程序的另一個實例存在。
每個EXE或DLL都是由段的集合組成,在Win32程序中,每個段以點(.)開頭。例如,當(dāng)編譯程序是編譯器時,則將所有代碼放入一個叫.text的段、將所有未初始化的數(shù)據(jù)放入.bss段、將所有初始化的數(shù)據(jù)放入.data段。
可以給每個段賦予一個或多個屬性(以下為常用的一些段屬性):
READ 段中的數(shù)據(jù)可讀
WRITE 段中的數(shù)據(jù)可寫
SHARED 段中的數(shù)據(jù)可被多個實例共享
EXECUTE 段中的數(shù)據(jù)可被執(zhí)行
可以用以下指令生成段:
#pragma data_seg("Shared")
static LONG g_lInstanceCount = -1;
#pragma data_seg()
編譯器生成這段代碼時,產(chǎn)生一個新段,并把它所在#pragma data_seg("Shared")指令后的初始化數(shù)據(jù)放入新段Shared,未初始化的數(shù)據(jù)放入.bss段。#pragma data_seg()以后的數(shù)據(jù)放回缺省數(shù)據(jù)段。
僅告訴編譯器把特定數(shù)據(jù)放入自己的段內(nèi)還不足以共享它們,還要告訴鏈接器在某一特定段內(nèi)變量要共享?梢栽阪溄訒r指定這個段的屬性。
/section:Shared,rws
段名 屬性
程序初始化時,例如調(diào)用WinMain函數(shù)時,調(diào)用InterlockedIncrement函數(shù)使共享段內(nèi)變量加1,就可以通過判斷共享段內(nèi)變量的值來判斷一個程序有幾個實例在運(yùn)行。以下代碼演示了如何判斷一個正在運(yùn)行的程序?qū)嵗沁@個程序的第一個實例。
BOOL bIsFirstInstance = (InterlockedIncrement(&g_lInstanceCount) == 0);
if(!bIsFirstInstance){
MessageBox(NULL, _TEXT("Screen Saver Launcher is already running."), g_szAppName,
MB_OK MB_ICONINFORMATION);
}
使共享段內(nèi)變量加1,沒使用 g_lInstanceCount ++,而是使用InterlockedIncrement(&g_lInstanceCount),因為InterlockedIncrement函數(shù)對變量的訪問進(jìn)行同步(Synchronize),阻止多個線程同時訪問同一個變量。有關(guān)線程同步的內(nèi)容請參閱有關(guān)Win32 SDK的文檔。
禁止多個Win32實例的方法很多,如Win32核心對象(Mutex, Semaphore)、全局原子等都可以用來禁止多個Win32實例,在這里我們只簡單地介紹以上兩種方法。
3.Screen Saver Launch:
屏幕保護(hù)程序是以scr為擴(kuò)展名的標(biāo)準(zhǔn)Windows可執(zhí)行程序。當(dāng)編輯可用屏幕保護(hù)程序的列表時,Control Panel Desktop Applet在Windows啟動目錄(Windows目錄和系統(tǒng)目錄)下查找擴(kuò)展名是scr的基于Windows的可執(zhí)行程序,如果Windows目錄和系統(tǒng)目錄下同時存在相同文件名的屏幕保護(hù)程序,則忽略Windows目錄下的那一個。任何蓄意的搗亂(如將文本文件或是基于DOS的可執(zhí)行文件擴(kuò)展名改為scr)Window95都不予理睬,但是將標(biāo)準(zhǔn)Windows可執(zhí)行程序的擴(kuò)展名改為scr時,Windows95及NT將不會察覺。這只是很極端的情況,相信用戶不會采用這種做法來"測試"你的Windows.
標(biāo)準(zhǔn)的基于Win32的屏幕保護(hù)程序必須按照嚴(yán)格的標(biāo)準(zhǔn)編寫,有關(guān)詳細(xì)介紹請參閱有關(guān)Win32 SDK文檔。這里需要提到的一點是所有的基于Win32的屏幕保護(hù)程序都要求有一個不超過25個字符的說明字符串。在屏幕保護(hù)程序的資源字符串表中,這個說明字符串的標(biāo)識必須是1。
但我們發(fā)現(xiàn)在Windows 95下的屏幕保護(hù)程序不完全是嚴(yán)格按照標(biāo)準(zhǔn)編寫的,當(dāng)編輯可用屏幕保護(hù)程序的列表時,Control Panel Desktop Applet只是簡單地把屏幕保護(hù)程序的文件名加入列表,而不是加入上面提及的說明字符串。而在Windows NT下,系統(tǒng)嚴(yán)格區(qū)分標(biāo)準(zhǔn)的和非標(biāo)準(zhǔn)的屏幕保護(hù)程序。對于標(biāo)準(zhǔn)的屏幕保護(hù)程序,系統(tǒng)取得它的說明字符串并將其顯示在屏幕保護(hù)程序的列表中;對于非標(biāo)準(zhǔn)的屏幕保護(hù)程序,系統(tǒng)只把它的文件名加入列表。
由于Windows 95和Windows NT下屏幕保護(hù)程序的列表顯示略有不同,所以這里分別加以說明。為區(qū)別起見,Windows 95下的SSLaunch用SSLaunch95表示,Windows NT下的SSLaunch用SSLaunchNT表示。
SSLaunch95 采用Window 95調(diào)用屏幕保護(hù)程序的方法,在Windows95的啟動目錄下搜索屏幕保護(hù)程序,把文件名加到任務(wù)欄通知區(qū)圖標(biāo)上下文菜單中,單擊鼠標(biāo)即可啟動相應(yīng)的屏幕保護(hù)程序。Windows 95把用戶選中的屏幕保護(hù)程序名保存在 System.ini文件中\(zhòng)boot\SCRNSAVE.EXE 下。SSLaunch95比較系統(tǒng)保存的用戶選中的屏幕保護(hù)程序名和搜索到的屏幕保護(hù)程序名,如果相同,則在任務(wù)欄通知區(qū)圖標(biāo)上下文菜單的相應(yīng)菜單項設(shè)置檢查標(biāo)志,以表示這個屏幕保護(hù)程序是否是當(dāng)前用戶選中的。SSLaunch95沒有判斷Windows啟動目錄下的屏幕保護(hù)程序是否是真正的屏幕保護(hù)程序,因為Windows 95下的Win32不能輕易地判斷一個scr文件是否是基于GDI的Windows可執(zhí)行文件(NE 或PE格式)。作者找到了兩個可用于判斷文件類型的函數(shù):SHGetFileInfo,GetBinaryType。SHGetFileInfo可以判斷出.exe、.com、.bat幾種文件類型,但認(rèn)為.scr文件不是可執(zhí)行文件;GetBinaryType可以輕易地判斷出文件類型,但Windows 95不支持,只是簡單地返回ERROR_NOT_IMPLEMENT,而Win32卻支持它。
點擊示意圖
SSLaunch95也可以在Windows NT 下運(yùn)行,不過彈出的上下文菜單不能用屏幕保護(hù)程序說明字符串填充,并且不能判斷scr是否是基于GDI的Windows可執(zhí)行程序。
下面介紹SSLaunchNT在Windows NT下對scr文件的判別,以及從scr文件資源中取得屏幕保護(hù)程序描述字符串的方法。
a.對scr文件的判別
Windows NT提供了對GetBinaryType函數(shù)的支持,因此,可用此函數(shù)判斷一個scr文件是否是Windows可執(zhí)行程序,并判斷出它是基于Win16還是 Win32的可執(zhí)行程序。這一點很重要,因為,對基于Win32的scr文件,我們在后面要取得它的字符串資源中的一個重要信息,及對屏幕保護(hù)程序的描述字符串。還應(yīng)注意的是,lpApplicationName應(yīng)給出全路徑,否則,它只在進(jìn)程所在的路徑下尋找文件,這樣會導(dǎo)致錯誤,從而不能返回在Windows啟動目錄下的.scr文件的信息。
BOOL GetBinaryType(
LPCTSTR lpApplicationName,
LPDWORD lpBinaryType
);
GetBinaryType調(diào)用成功后,lpBinaryType指向的DWORD返回以下值:
SCS_32BIT_BINARY 基于Win32的應(yīng)用程序
SCS_DOS_BINARY 基于MS-DOS的應(yīng)用程序
SCS_OS216_BINARY 基于16位OS/2的應(yīng)用程序
SCS_PIF_BINARY MS-DOS應(yīng)用程序的PIF 文件
SCS_POSIX_BINARY 基于POSIX的應(yīng)用程序
SCS_WOW_BINARY 基于16位Windows的應(yīng)用程序
b.從scr文件字符串資源中取得屏幕保護(hù)文件描述字符串
當(dāng)我們判斷出了一個基于Win32的scr文件后,就可以著手取得它的字符串。在Win32中,有一種簡單有效的方法:把一個EXE或DLL文件以數(shù)據(jù)文件方式加載,調(diào)用LoadLibraryEx函數(shù)。
HINSTANCE LoadLibraryEx(
LPCTSTR lpLibFileName, // EXE或DLL文件名
HANDLE hFile, // 保留參數(shù),必須為NULL
DWORD dwFlags // 函數(shù)入口標(biāo)志
);
dwFlags可以是0或以下標(biāo)志的組合:
DON'T_RESOLVE_DLL_REFERENCES 系統(tǒng)將DLL映射到進(jìn)程的地址空間而不調(diào)用DllMain函數(shù)。
LOAD_LIBRARY_AS_DATAFILE 系統(tǒng)將DLL象一個數(shù)據(jù)文件那樣映射到進(jìn)程的地址空間,而不調(diào)用DllMain函數(shù)。如果要取得EXE中的資源,也可調(diào)用LoadLibraryEx函數(shù)把EXE映射到進(jìn)程地址空間。
LOAD_WITH_ALTERED_SEARCH_PATH 將改變LoadLibraryEx在定位DLL文件時所采用的方法。
當(dāng)以LOAD_LIBRARY_AS_DATAFILE的方式調(diào)用LoadLibraryEx時,系統(tǒng)只是簡單地創(chuàng)建一個文件映象對象,把DLL(EXE)映射到本進(jìn)程的地址空間,并不調(diào)用DllMain(WinMain)。如果調(diào)用成功,則函數(shù)返回一個HINSTANCE,即被映射到本進(jìn)程地址空間的DLL(EXE)的裝入地址,這樣,就可以調(diào)用LoadString函數(shù),從DLL(EXE)文件的字符串資源表中取得指定的字符串。
點擊示意圖
這里仍需指出的是,必須判斷LoadString函數(shù)調(diào)用是否成功,因為有些scr文件(即使是基于Win32的)也有可能是非標(biāo)準(zhǔn)的(如Windows 95下的大多數(shù)scr文件),如果LoadString調(diào)用失敗,則SSLaunchNT用文件名取代scr的描述字符串填入SSLaunchNT上下文菜單的菜單項。