從任務通知區啟動螢幕保護程式 (轉)

worldblog發表於2007-12-04
從任務通知區啟動螢幕保護程式 (轉)[@more@]

從任務通知區啟動螢幕保護


     朱志強
  本文透過一個啟動螢幕保護程式的小程式aunch,來介紹應用程式如何向工作列通知區加入圖示、如何禁止多個例項以及螢幕保護程式的有關內容。

  SSLaunch用C語言編寫,用Visual C++ 5.0編譯,是一個基於無對話方塊的程式,同時禁止多個例項,即一次只能有一個例項執行。工作列通知區圖示在對話方塊初始化時加入,對話方塊響應程式定義的回撥訊息,當滑鼠左鍵按下時,彈出一由螢幕保護程式名填充的上下文選單。對話方塊關閉(即程式退出)時刪除工作列通知區圖示。如果讀者有興趣可以很容易地把它移植成基於 MFC 的程式。

  1、工作列通知區

   95的工作列中有一個通知區, 應用程式可以把一個圖示放入其中,以表示操作狀態,並可以有與之相關聯的工具用作說明控制。當滑鼠出現在此圖示的矩形邊界內時,向相應的應用程式傳送應用程式定義的回撥訊息。 應用程式透過傳送訊息增加、修改、刪除工作列圖示。訊息的傳送透過_NotifyIcon來完成,如果呼叫成功,則返回TRUE;否則,返回FALSE。Shell_NotifyIcon函式原形如下:
WINSHELL BOOL WINAPI Shell_NotifyIcon(
  D dwMessage, // 訊息識別符號
  PNOTIFYICONDATA pnid // NOTIFYICONDATA 結構
  );
     訊息識別符號可以是 :
  NIM_ADD 向工作列通知區加入圖示
  NIM_DELETE 從工作列通知區刪除圖示
  NIM_MODIFY 改變工作列通知區圖示
     NOTIFYICONDATA 結構:
  typedef struct _NOTIFYICONDATA {
   DWORD cbSize;
   HWND hWnd;
   UINT uID;
   UINT uFlags;
  UINT uCallbackMessage;
  HICON hIcon;
  char szTip[64];
  } NOTIFYICONDATA, *PNOTIFYICONDATA;
     其中:
  cbSize NOTIFYICONDATA 結構大小
  hWnd 接收回撥訊息視窗控制程式碼
  uID 工作列通知區圖示標識
  uFlags 指定該結構中那些成員有效
  uCallbackMessage 應用程式定義的回撥訊息
  hIcon 工作列通知區圖示控制程式碼
  szT工作列通知區提示字串
     引數uFlags可以是下列值的組合:
  NIF_ICON 工作列通知區圖示有效
  NIF_MESSAGE 應用程式定義的回撥訊息有效
  NIF_TIP 工作列通知區提示字串有效
  a.工作列通知區圖示的加入
  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),

  KEINTRE(IDI_SSLAUNCH));

   lstrcpyn(nid.szTip,g_szAppName,sizeof(nid.szTip) /sizeof(nid.szTip[0]));

  

   return(Shell_NotifyIcon(NIM_ADD, &nid))

  }

  b.工作列通知區圖示的刪除

   應用程式退出時,應該刪除任務通知區上相應的圖示:
  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.應用程式定義回撥訊息的接收

  若為工作列通知區指定了回撥訊息,則會於滑鼠事件在此區域發生時

  嚮應用程式傳送此訊息,其中wParam是工作列通知區圖示標識,lParam

  是滑鼠事件發生後的滑鼠資訊。

  

  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 somthing

   break;

  

   case WM_LBUTTONUP :

   //Do somthing

   break;

  

   default :

   break;

   }

   }

  }

  

  2.禁止多個Win32例項

  在討論禁止多個Win32例項之前,我們先討論一下WinMain函式。我們知道,任何一個基於GDI的Windows程式以WinMain函式作為入口被系統呼叫。在Win16中,hPrevInstance指向前一個例項的控制程式碼,但在Win32中,每一個程式都有一個獨立的4G地址空間,從0到2G屬於程式私有,對其他程式來說是不可見的。所以,在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 函式查詢指定視窗,如果成功,則返回要找的視窗的控制程式碼,否則返回NULL,由此可判斷是否有程式的另一個例項存在。

  

  下圖的程式碼片段演示如何使用FindWindow函式:

  

  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);

  }

  

  需要注意的是,很可能程式的各個例項有不同的視窗名,如果象下面這樣呼叫FindWindow

   HWND hWnd = FindWindow(szClassName,NULL);

  則查詢所有的視窗並匹配視窗類名,如果你能保證你的視窗類名是唯一的,那麼你可以信賴FindWindow,否則,你需要用更好的方法。

  

  方法二

  

  透過在EXE之間共享資料段從而共享資料來判斷是否有程式的另一個例項存在。

  每個EXE或DLL都是由段的集合組成,在Win32程式中,每個段以點(.)開頭。例如,當編譯程式是時,則將所有程式碼放入一個叫.text的段、將所有未初始化的資料放入.bss段、將所有初始化的資料放入.data段。

  

  可以給每個段賦予一個或多個屬性(以下為常用的一些段屬性):

  

  READ 段中的資料可讀

  WRITE 段中的資料可寫

  SHARED 段中的資料可被多個例項共享

  EXECUTE 段中的資料可被

  

  可以用以下指令生成段:

  #pragma data_seg("Shared")

  static LONG g_lInstanceCount = -1;

  #pragma data_seg()

  

  編譯器生成這段程式碼時,產生一個新段,並把它所在#pragma data_seg("Shared")指令後的初始化資料放入新段Shared,未初始化的資料放入.bss段。#pragma data_seg()以後的資料放回預設資料段。

  

  僅告訴編譯器把特定資料放入自己的段內還不足以共享它們,還要告訴連結器在某一特定段內變數要共享。可以在連結時指定這個段的屬性。

  /section:Shared,rws

   段名 屬性

  

  程式初始化時,例如呼叫WinMain函式時,呼叫InterlockedIncrement函式使共享段內變數加1,就可以透過判斷共享段內變數的值來判斷一個程式有幾個例項在執行。以下程式碼演示瞭如何判斷一個正在執行的程式例項是這個程式的第一個例項。

  

  BOOL bIirstInstance = (InterlockedIncrement(&g_lInstanceCount) == 0);

   if(!bIsFirstInstance){

   MessageBox(NULL, _TEXT("Screen Saver Launcher is already running."), g_szAppName,

   MB_OK | MB_ICONINFORMATION);

   }

  

  使共享段內變數加1,沒使用 g_lInstanceCount ++,而是使用InterlockedIncrement(&g_lInstanceCount),因為InterlockedIncrement函式對變數的訪問進行同步(Synchronize),阻止多個執行緒同時訪問同一個變數。有關執行緒同步的內容請參閱有關Win32 SDK的文件。

  禁止多個Win32例項的方法很多,如Win32核心(Mutex, Semaphore)、全域性原子等都可以用來禁止多個Win32例項,在這裡我們只簡單地介紹以上兩種方法。

  

  3.Screen Saver Launch:

  螢幕保護程式是以scr為副檔名的標準Windows可執行程式。當編輯可用螢幕保護程式的列表時,Control Panel Desktop Applet在Windows啟動目錄(Windows目錄和系統目錄)下查詢副檔名是scr的基於Windows的可執行程式,如果Windows目錄和系統目錄下同時存在相同名的螢幕保護程式,則忽略Windows目錄下的那一個。 任何蓄意的搗亂(如將文字檔案或是基於DOS的可執行副檔名改為scr)Window95都不予理睬,但是將標準Windows可執行程式的副檔名改為scr時,Windows95及NT將不會察覺。這只是很極端的情況,相信不會採用這種做法來"測試"你的Windows.

  標準的基於Win32的螢幕保護程式必須按照嚴格的標準編寫,有關詳細介紹請參閱有關Win32 SDK文件。這裡需要提到的一點是所有的基於Win32的螢幕保護程式都要求有一個不超過25個字元的說明字串。在螢幕保護程式的資源字串表中,這個說明字串的標識必須是1。

  但我們發現在Windows 95下的螢幕保護程式不完全是嚴格按照標準編寫的,當編輯可用螢幕保護程式的列表時,Control Panel Desktop Applet只是簡單地把螢幕保護程式的檔名加入列表,而不是加入上面提及的說明字串。而在下,系統嚴格區分標準的和非標準的螢幕保護程式。對於標準的螢幕保護程式,系統取得它的說明字串並將其顯示在螢幕保護程式的列表中;對於非標準的螢幕保護程式,系統只把它的檔名加入列表。

  由於Windows 95和Windows NT下螢幕保護程式的列表顯示略有不同,所以這裡分別加以說明。為區別起見,Windows 95下的SSLaunch用SSLaunch95表示,Windows NT下的SSLaunch用SSLaunchNT表示。

  SSLaunch95 採用Window 95呼叫螢幕保護程式的方法,在Windows95的啟動目錄下搜尋螢幕保護程式,把檔名加到工作列通知區圖示上下文選單中,單擊滑鼠即可啟動相應的螢幕保護程式。Windows 95把使用者選中的螢幕保護程式名儲存在 System.ini檔案中bootSCRNSAVE.EXE 下。SSLaunch95比較系統儲存的使用者選中的螢幕保護程式名和搜尋到的螢幕保護程式名,如果相同,則在工作列通知區圖示上下文選單的相應選單項設定檢查標誌,以表示這個螢幕保護程式是否是當前使用者選中的。SSLaunch95沒有判斷Windows啟動目錄下的螢幕保護程式是否是真正的螢幕保護程式,因為Windows 95下的Win32不能輕易地判斷一個scr檔案是否是基於GDI的Windows可執行檔案(NE 或PE格式)。作者找到了兩個可用於判斷檔案型別的函式:SHGetFileInfo,GetBinaryType。SHGetFileInfo可以判斷出.exe、.com、.bat幾種檔案型別,但認為.scr檔案不是可執行檔案;GetBinaryType可以輕易地判斷出檔案型別,但Windows 95不支援,只是簡單地返回ERROR_NOT_IMPLEMENT,而Win32卻支援它。

  點選示意圖

  SSLaunch95也可以在Windows NT 下執行,不過彈出的上下文選單不能用螢幕保護程式說明字串填充,並且不能判斷scr是否是基於GDI的Windows可執行程式。

  下面介紹SSLaunchNT在Windows NT下對scr檔案的判別,以及從scr檔案資源中取得螢幕保護程式描述字串的方法。

  a.對scr檔案的判別

  Windows NT提供了對GetBinaryType函式的支援,因此,可用此函式判斷一個scr檔案是否是Windows可執行程式,並判斷出它是基於Win16還是 Win32的可執行程式。這一點很重要,因為,對基於Win32的scr檔案,我們在後面要取得它的字串資源中的一個重要資訊,及對螢幕保護程式的描述字串。還應注意的是,lpApplicationName應給出全路徑,否則,它只在程式所在的路徑下尋找檔案,這樣會導致錯誤,從而不能返回在Windows啟動目錄下的.scr檔案的資訊。

  

  BOOL GetBinaryType(

  LPCTSTR lpApplicationName,

  LPDWORD lpBinaryType

  );

  GetBinaryType呼叫成功後,lpBinaryType指向的DWORD返回以下值:

  SCS_32BIT_BINARY 基於Win32的應用程式

  SCS_DOS_BINARY 基於MS-DOS的應用程式

  SCS_OS216_BINARY 基於16位OS/2的應用程式

  SCS_PIF_BINARY MS-DOS應用程式的PIF 檔案

  SCS_POSIX_BINARY 基於POSIX的應用程式

  SCS_WOW_BINARY 基於16位Windows的應用程式

  b.從scr檔案字串資源中取得螢幕保護檔案描述字串

  當我們判斷出了一個基於Win32的scr檔案後,就可以著手取得它的字串。在Win32中,有一種簡單有效的方法:把一個EXE或DLL檔案以資料檔案方式載入,呼叫LoadLibraryEx函式。

  HINSTANCE LoadLibraryEx(

  LPCTSTR lpLibFileName, // EXE或DLL檔名

  HANDLE hFile, // 保留引數,必須為NULL

  DWORD dwFlags // 函式入口標誌

  );

  dwFlags可以是0或以下標誌的組合:

  DONT_RESOLVE_DLL_REFERENCES 系統將DLL對映到程式的地址空間而不呼叫DllMain函式。

  LOAD_LIBRARY_AS_DATAFILE 系統將DLL象一個資料檔案那樣對映到程式的地址空間,而不呼叫DllMain函式。如果要取得EXE中的資源,也可呼叫LoadLibraryEx函式把EXE對映到程式地址空間。

  LOAD_WITH_ALTERED_SEARCH_PATH 將改變LoadLibraryEx在定位DLL檔案時所採用的方法。

  

  當以LOAD_LIBRARY_AS_DATAFILE的方式呼叫LoadLibraryEx時,系統只是簡單地建立一個檔案映象物件,把DLL(EXE)對映到本程式的地址空間,並不呼叫DllMain(WinMain)。如果呼叫成功,則函式返回一個HINSTANCE,即被對映到本程式地址空間的DLL(EXE)的裝入地址,這樣,就可以呼叫LoadString函式,從DLL(EXE)檔案的字串資源表中取得指定的字串。

  點選示意圖

  這裡仍需指出的是,必須判斷LoadString函式呼叫是否成功,因為有些scr檔案(即使是基於Win32的)也有可能是非標準的(如Windows 95下的大多數scr檔案),如果LoadString呼叫失敗,則SSLaunchNT用檔名取代scr的描述字串填入SSLaunchNT上下文選單的選單項。

  (哈爾濱泛微電子工程公司 150001 朱志強)

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988012/,如需轉載,請註明出處,否則將追究法律責任。

相關文章