程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> Web版本檢查以及為應用程序添加聲音效果

Web版本檢查以及為應用程序添加聲音效果

編輯:關於VC++

在 2003 四月的專欄文章中,你描述了如何實現一個叫 CWebVersion 的類,用它可以存取網絡上的某個文件來檢查軟件的版本,當版本過期後提示用戶更新程序。你的實現使用 FTP 來下載文件,但我的站點的 ISP 不允許使用匿名 FTP 連接,只能通過用戶和口令登陸。我能不能用 HTTP 來代替 FTP,將版本文件作為 Web 頁面下載。

如果沒讀過 2003 四月的專欄文章,我在這裡簡單介紹一下 CWebVersion 類,它是我編寫的一個用來比較程序版本號的類,版本號文件存儲在 Web 上。我在 TraceWin 程序中就是使用這個類來通知用戶何時有新版本下載的。

沒錯,你可以用 HTTP;但不必轉換文件。在我原來的實現中確實應該使用 HTTP,因為 HTTP 比 FTP 使用的更加廣泛。許多 Web 服務提供商出於安全的原因都不允許匿名的 FTP 訪問,但對於文件傳輸來說,FTP 效率更高(這也是我用 FTP 的原因),HTTP 對於獲取簡單的文本文件不錯。

CWebVersion 讀取文本文件,文件中的版本數據用逗號分割成四部分:高/低位的主/次版本號。使用方法是這樣的:

if (CWebVersion::Online()) {
  CWebVersion webver("www.mysite.com");
  if (webver.ReadVersion("myversion.txt")) {
    // dwVersionMS and dwVersionLS now
    // hold the version numbers
  }
}

靜態成員函數 CWebVersion::Online 調用 ::InternetQueryOption, 用 INTERNET_OPTION_CONNECTED_STATE 作為參數,以便檢查此電腦是否連接到 Internet。如果已經連接,那麼 CWebVersion::ReadVersion 便從你的 Web 網站讀取版本文件。接著你可以將讀取到的版本號與應用程序中編譯的版本號進行比較,這個版本號通常在 VERSIONINFO 或 DllGetVersion 資源中(詳情參見:“如何獲取某個動態鏈接庫的版本信息”)。原來的 CWebVersion 使用 FTP 來獲取文件;本文我改為使用 HTTP 來處理。使用 MFC 的 Wininet 類,在 Web 上通過 HTTP 讀取文件很容易:

// in CWebVersion::ReadVersion
CInternetSession session(_T("MySession"));
CHttpConnection* pConn =
session.GetHttpConnection("www.dilascia.com",INTERNET_DEFAULT_HTTP_PORT);
CHttpFile* pFile =
pConn->OpenRequest(CHttpConnection::HTTP_VERB_GET, "TraceWinVer.txt");
pFile->SendRequest();

上面的代碼意圖是想下載文件 www.dilascia.com/TraceWinVer.txt。 在調用了 SendRequest 之後,你可以調用 CHttpFile::QueryInfoStatusCode 來獲取狀態嗎——例如,文件沒找到的狀態碼是 404,200 表示成功(完整的狀態碼列表參見 wininet.h 頭文件)——接著調用 CHttpFile::Read 將文件讀入你的緩沖,這個工作由 CWebVersion::ReadVersion 完成,然後調用 scanf ,根據 “Mhi,Mlo,mhi,mlo” 格式解析文件內容,此處 Mhi,Mlo,mhi,mlo 分別代表主版本和次版本號的高位和低位字(WORDs)。CWebVersion 將這些信息保存在 CWebVersion::dwMajorVersion 和 CWebVersion::dwMinorVersion 中。完整的代碼參見 Figure 1。

為了測試 CWebVersion,我寫了一個程序 GetVersion.exe(參見 Figure 2),當我在我自己的網站上首次測試 CWebVersion 時,我將版本文件命名為 TraceWinVer.dat。雖然文件已經到位,但下載時報404錯誤(文件不存在)。開始我以為必須在請求頭中添加 .dat 接受文件類型:

static LPCTSTR MyHeaders = _T("Accept: text/dat\r\n");
...
pHttpFile->AddRequestHeaders(MyHeaders);

Figure 2 測試程序

但是,這樣做並沒有解決問題。仍然報404錯誤。最後查出原因是我的網站服務提供商出於安全考慮將 .dat 文件擴展名屏蔽掉了,他們倒是樂意修改配置,但我傾向於保持安全性,因此選擇將我的版本文件改名為 TraceWinVer.txt。畢竟它本來就是一個文本文件。

如果你使用的是 Microsoft .NET Framework,那麼可以用 HttpWebRequest 和 HttpWebResponse 通過 HTTP 來取得文件,而不是 MFC。使用 .NET Framework,你用完整的 URL 創建一個 HttpWebRequest,然後調用 GetResponse 發送請求並獲得響應:

HttpWebRequest* req = dynamic_cast<HttpWebRequest*>( WebRequest::Create(S"http://www.dilascia.com/TraceWinVer.txt"));
req->Timeout = 5000; // 5 sec
HttpWebResponse* resp = dynamic_cast<HttpWebResponse*>(req->GetResponse());

這裡 dynamic_cast 必須使用 HTTP 專用的屬性和方法 HttpWebRequest 和 HttpWebResponse。如果你使用 Visual Studio 2005 中所帶的 C++/CLI,那麼用(^)(tracking handles)代替指針,並且不必在處理托管串文字量是使用 S。在 .NET 中,如果要讀取文件,先在響應流中創建一個 StreamReader,然後再讀取它的內容,就像下面這樣:

StreamReader* strm = new StreamReader(resp->GetResponseStream(), encoding);
String* content = strm->ReadToEnd();
strm->Close();

我為 .NET 開發人員寫了一個完整的 GetVersion 托管 C++ 程序,代碼都在本文附帶的源代碼下載文件中。

如何在基於 MFC 的應用程序中添加聲音效果(不僅僅是用 MessageBeep 函數發出的蜂鳴聲)?

將聲音添加到基於 MFC 的應用程序並不難,但在我講解如何做之前,得先提醒你,沉默是金,在軟件中尤其如此。雖然很多地方都可以用聲音(如生成失敗、e-mail到達以及該購買雜物了),在大多數情況下,最好保持安靜。

從設計上來說,軟件裡添加聲音純屬沒事找事,不知你訪問過那種一打開主頁就播放叮當聲的網站沒有?大多數人的第一反應是按“返回”按鈕。如果你必須添加聲音,請不要忘了在程序的“工具|選項”菜單中提供一個“靜音”選項設置。

OK,現在言規正傳,Windows 中有一個函數叫 PlaySound 可以做你想要做的事情。這個函數的定義在 mmsystem.h 頭文件中,你必須與winmm.lib 鏈接。PlaySound 播放聲音,它的參數之一是聲音文件名或者資源名。下面是一個調用例子:

PlaySound("woofwoof.wav",NULL,SND_NODEFAULT);

這裡的專用標志 SND_NODEFAULT 告訴 Windows:如果找不到聲音文件的話,不要播放默認的聲音(MessageBeep)。其它標志參見 Figure 3。Windows 的函數眾多,使用 PlaySound 的方式也多種多樣,很多都沒有文檔可查。一些標志我一直也很迷惑。不過不要怕,我會解開這些迷。

播放聲音最有效的方式之一是用 SND_APPLICATION 標志,它播放應用程序關聯的聲音。例如:

PlaySound("AppExit",NULL, SND_APPLICATION|SND_NODEFAULT);

Figure 4 Happy Sounds

上面的代碼是播放聲音 AppExit。那麼這個代碼到底做了些什麼呢?首先 Windows 查看注冊表:HKCU\AppEvents\Schemes\Apps\AppExit,然後讀取 .current 的值。如果 .current 的值是一個文件名,比如 exit.wav,Windows 便播放這個聲音文件。Windows 按照以下順序搜索目錄:當前目前,Windows 目錄,Windows 系統目錄和 PATH 環境變量指定的目錄。為什麼應用程序關聯的聲音這麼酷呢?因為用戶可以通過控制面板來定制它們(參見 Figure 4)。PlaySound 還用到了一個 SND_RESOURCE 標志,這個標志使你能播放來自資源文件的聲音。為此你首先得將聲音添加到資源文件(.rc)中,就像下面這樣:

AppExit WAVE "res\\STExit.wav"

注意資源必須是一個 WAVE 文件——這個重要的細節到目前為止微軟沒有在文檔中說明。資源編譯器會將WAV文件嵌入EXE可執行文件,這樣你就可以像下面這樣用 SND_RESOURCE 標志播放它了:

PlaySound("AppExit", AfxGetResourceHandle(), SND_RESOURCE);

PlaySound 需要包含資源的模塊句柄,這個模塊句柄可以通過調用 AfxGetResourceHandle (在大多數應用程序中,它獲得的結果與 AfxGetInstanceHandle 相同)來獲得。在前面的代碼段中,資源標示符是一個字符串(“AppExit”),但如果你指定了 SND_ALIAS_ID 標志,也可以用一個整數 ID。

為了簡化開發,我寫了一個類:CSoundMgr,用這個類可以很容易在程序實現聲音效果。CSoundMgr 可以讓你定義邏輯聲音並通過 ID 來播放。該類具備注冊聲音的函數,通過修改一個標志便可以讓你的程序驟然間打破沉默。CSoundManager 甚至可以在你的資源文件中搜索默認的聲音。為了示范該類的使用方法,我寫了一個測試程序——SoundTest。它是一個典型的 MFC 基於對話框的程序。如 Figure 5 所示,程序運行畫面中顯示了應用程序當前的五個聲音值。

Figure 5 SoundTest

SoundTest 定義聲音的第一步是創建聲音的 IDs。我用了一個枚舉類型來處理五個聲音:MYSND_HAPPY,MYSND_UNHAPPY 等等。注意不要使用 0 作為聲音的 ID;因為 CSoundMgr::PlaySound(0) 停止播放當前的聲音,作用相當於 ::PlaySound(NULL, NULL, 0)。定義了 IDs 之後,你便可以使用在 SoundMgr.h 文件中定義宏來建立聲音映射表,就像下面這樣:

BEGIN_SOUND_MAP(MySounds)
DEFINE_SOUND(MYSND_HAPPY, _T("ST_Happy"), _T("SoundTest Happy"))
...
END_SOUND_MAP()

表的每一行都有三項:ID,邏輯名和 GUI界面名。ID 用於播放聲音,邏輯名(例如:ST_Happy)給注冊表鍵值以及默認的聲音資源內部使用。GUI界面名是顯示在用戶界面上給用戶看的,當用戶使用控制面板中的聲音配置小程序時,用戶看到的就是 GUI界面名——例如,Figure 4 中顯示的“SoundTest Happy”。表一旦定義好,下一步是創建一個 CSoundMgr 實例,用這個表的值來初始化這個實例:

// 該程序的聲音管理器
CSoundMgr SoundMgr(MySounds);

整個程序只需要一個 CSoundMgr 即可。最後,如果你想將默認的聲音內嵌在程序中,那麼你必須添加相應的 WAVE 資源,每個邏輯聲音名對應一個 WAVE 文件。例如:

ST_HAPPY WAVE "res\\STHappy.wav"
ST_UNHAPPY WAVE "res\\STUnhappy.wav"

現在聲音都定義好了,要播放聲音只需用下面的方法即可:

SoundMgr.PlaySound(MYSND_HAPPY)

此處程序播放聲音 MYSND_HAPPY。大概過程是這樣的惡:CSoundMgr 首先查找注冊表鍵/值,CSoundMgr::PlaySound 播放

HKCU\AppEvents\Schemes\Apps\SoundTest\ST_Happy\.current

如果這個鍵/值不存在,你可以調用 CSoundMgr::IsRegistered 檢查你的聲音是否被注冊過——如果沒有,調用 CSoundMgr::Register 注冊它們:

if (!SoundMgr.IsRegistered())
SoundMgr.Register();

CSoundMgr::Register 創建所有需要的注冊鍵,以便用戶在控制面板中定制聲音。此時它實際上不需要給注冊鍵任何賦值,讓它們為空,以便 CSoundMgr::PlaySound 使用默認的聲音資源。如果你不想使用默認的聲音,那就不要為它們創建資源或者用下面的方法屏蔽:

CSoundMgr::m_bUseResourceSounds = FALSE;

CSoundMgr 的實現很簡單。大多數代碼都是在處理注冊表的存取,它也許是 Windows 編程中最繁瑣的事情之一了(具體細節參見 Figure 6)。好在我把很多繁瑣的工作都完成了。每個邏輯聲音的子鍵位於 HKCU\AppEvents\Schemes\Apps\progname,此處 progname 是你的程序名,這個名字與程序“Settings”中使用的字符串名相同,也就是::AfxGetAppName 返回的值。 每個子鍵在 .current 中保存聲音文件名。CSoundMgr 並不創建這個.current,它只建立空鍵,因為當用戶改變聲音時,控制面板中的聲音配置程序創建 .current。每個邏輯聲音鍵的缺省值是聲音人類可讀的 GUI 名,但據我所知 Windows 忽略這個值;它在一個別的鍵中查找 GUI 名:HKCU\AppEvents\EventNames。詳情請見代碼說明。

所以你得為每個邏輯聲音名創建另外一組鍵,其缺省值是人類可讀的 GUI 名。當然,如果你使用 CSoundMgr,就可以不用考慮這些注冊表鍵。只要定義你的聲音並調用 CSoundMgr::Register 即可。如果用戶已經定義了聲音,調用 Register 還不能生效,僅僅是創建那些還不存在的鍵。如果你想實現一個重置命令將聲音恢復到其默認值,你應該刪除HKCU\AppEvents\Schemes\Apps\progname,然後再次調用 Register。

最後是一個小小的建議:在選擇你的邏輯名稱時,要小心避免與其它應用程序沖突。糟糕的是所有事件名在HKCU\AppEvents\EventNames裡的相同名字空間中,所以我建議使用前綴,就像我在例子 SoundTest 中所做的那樣,邏輯名都以 ST_開始。這樣也很容易找到,因為 REGEDT32 是按照字母順序列出鍵名的。

SoundTest 有一個啟用/屏蔽聲音的復選框,用戶可以用它關閉聲音。這個按鈕關聯的命令處理例程與變量 CSoundMgr::m_bEnableSounds 綁定。具體細節參見代碼。

編程愉快!

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved