2008年(884)
分类: C/C++
2008-08-06 10:03:47
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.