首页 > 编程资源分享区 > C/C++源代码共享 > Web 版本检查,为应用程序添加声音
2006
09-22

Web 版本检查,为应用程序添加声音










原著:Paul DiLascia
翻译:NorthTibet


下载源代码:CAtWork0605.exe (163KB)
原文出处:Web Version Checking, Adding Sound to an App



 在 2003 四月的专栏文章中,你描述了如何实现一个叫 CWebVersion 的类,用它可以存取网络上的某个文件来检查软件的版本,当版本过期后提示用户更新程序。你的实现使用 FTP 来下载文件,但我的站点的 ISP 不允许使用匿名 FTP 连接,只能通过用户和口令登陆。我能不能用 HTTP 来代替 FTP,将版本文件作为 Web 页面下载。

Mark Simpson


如果没读过 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_DEFA ULT_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(MyHead ers);


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 函数发出的蜂鸣声)?

Alexander Potapenko

将声音添加到基于 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 绑定。具体细节参见代码。

编程愉快!


您的提问和评论可发送到 Paul 的信箱:cppqa@microsoft.com.
 

作者简介
  Paul DiLascia
是一名自由作家,软件咨询顾问以及大型 Web/UI 的设计师。他是《Writing Reusable Windows Code in C++》书(Addison-Wesley, 1992)的作者。业余时间他开发 PixeLib,这是一个 MFC 类库,从 Paul 的网站 http://www.dilascia.com 可以获得这个类库。
.
本文出自 MSDN MagazineMay 2006 期刊,可通过当地报摊获得,或者最好是 订阅


留下一个回复