2008年(909)
分类:
2008-05-06 22:40:06
原著:Paul DiLascia
翻译:NorthTibet
下载源代码:CAtWork0605.exe (163KB)
原文出处:Web
Version Checking, Adding Sound to an App
在
2003 四月的专栏文章中,你描述了如何实现一个叫 CWebVersion
的类,用它可以存取网络上的某个文件来检查软件的版本,当版本过期后提示用户更新程序。你的实现使用 FTP 来下载文件,但我的站点的 ISP
不允许使用匿名 FTP 连接,只能通过用户和口令登陆。我能不能用 HTTP 来代替 FTP,将版本文件作为 Web 页面下载。
if (CWebVersion::Online()) { CWebVersion webver(""); 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("",INTERNET_DEFAULT_HTTP_PORT); CHttpFile* pFile = pConn->OpenRequest(CHttpConnection::HTTP_VERB_GET, "TraceWinVer.txt"); pFile->SendRequest();
上面的代码意图是想下载文件 /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( WebRequest::Create(S"http:///TraceWinVer.txt")); req->Timeout = 5000; // 5 sec HttpWebResponse* resp = dynamic_cast (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 函数发出的蜂鸣声)?
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
绑定。具体细节参见代码。
编程愉快!
您的提问和评论可发送到 Paul 的信箱:cppqa@microsoft.com.
下载本文示例代码
C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...C At Work 专栏...