Related Articles
Our security experts present 10 vulnerable pieces of code. Your mission is to find the holes (a.k.a. bad security practices) in the code. Michael Howard and Bryan Sullivan MSDN Magazine November 2008 ... Read more!
This month Dino Esposito explains how the browser interoperability layer in Silverlight addresses a number of your Silverlight / Web page interaction needs. Dino Esposito MSDN Magazine November 2008 ... Read more!
Find out how to integrate SharePoint and Silverlight by creating a Silverlight media player and deploying it as a SharePoint Web Part. Steve Fox and Paul Stubbs MSDN Magazine November 2008 ... Read more!
With Windows Presentation Foundation (WPF) you can lay out text on a path, then animate the individual points defining the path and watch the characters bounce around in response. Charles Petzold MSDN Magazine December 2008 ... Read more!
Silverlight is powerful enough to let you easily build an image magnification feature for you web site with very little code, most of which is XAML. Find out how. Jeff Prosise MSDN Magazine November 2008 ... Read more!
Also by this Author
Paul DiLascia MSDN Magazine July 2005 ... Read more!
Paul DiLascia MSDN Magazine January 2006 ... Read more!
Paul DiLascia MSDN Magazine April 2006 ... Read more!
Paul DiLascia MSDN Magazine October 2005 ... Read more!
Paul DiLascia MSDN Magazine December 2005 ... Read more!
Popular Articles
Learn how to automate custom SharePoint application deployments, use the SharePoint API, and avoid the hassle of custom site definitions. E. Wilansky, P. Olszewski, and R. Sneddon MSDN Magazine May 2008 ... Read more!
Here are some design patterns that allow you to achieve higher cohesion and looser coupling for more flexible, reusable applications. Jeremy Miller MSDN Magazine October 2008 ... Read more!
C# allows developers to embed XML comments into their source files-a useful facility, especially when more than one programmer is working on the same code. The C# parser can expand these XML tags to provide additional information and export them to an external document for further processing. This article shows how to use XML comments and explains the relevant tags. The author demonstrates how to set up your project to export your XML comments into convenient documentation for the benefit of other developers. He also shows how to use comments ...
Read more!
C# 2.0 introduces a wealth of exiting new features, such as generics, iterators, partial classes and anonymous methods. While generics are the most talked-about feature especially for former classic C++ developers, the rest of the new features are important additions to your .NET development arsenal, enhancing power and improving overall productivity. This article is dedicated to all the new C# 2.0 capabilities besides generics to give you a good overall picture of the upcoming features. Juval Lowy MSDN ...
Read more!
Jeff Prosise explains when it's better to use UpdatePanel and when it's better to use asynchronous calls to WebMethods or page methods instead. Jeff Prosise MSDN Magazine June 2007 ... Read more!
Our Blog
Silverlight provides a browser interoperability layer that allows managed code to access the document object model (DOM) of the underlying page. At the same time, JavaScript code running in the page can access the XAML content of the plug-in and even make modifications. In the November 2008 issue of MSDN Magazine, Dino Esposito discusses the ...
Read more!
So many factors can affect the performance of a Web page—the distance between server and client, the size of the elements on the page, how the browser loads these elements, available bandwidth. Finding those bottlenecks and identifying the culprits is no easy task. In the November 2008 issue of MSDN Magazine, Jim Pierson introduces ...
Read more!
Choosing the best alternative is a common task in software development and testing. A group of beta users may need to choose the best user interface from a set of prototypes. Or imagine the members of an open source project voting for a policy. In the November 2008 issue of MSDN Magazine, Dr. James McCaffrey describes five of the ...
Read more!
It’s helpful to think about secure design from a more holistic perspective by using threat models to drive your security engineering process. In the November 2008 issue of MSDN Magazine, Michael Howard proposes using the threat model to help drive other SDL security requirements, primarily code review priority, fuzz testing priority, ...
Read more!
C# developers can use the Visual Studio Tools for the Office System (VSTO) Power Tools Office interop API extensions to streamline Office application development. The extensions provide a thin, strongly typed layer over the loosely typed Office object models. In the December 2008 issue of MSDN Magazine, Andrew Whitechapel, Phillip Hoff, and Vladimir Morozov walk you through developing ...
Read more!
|
C++ At Work
Web Version Checking, Adding Sound to an App
Paul DiLascia
Code download available at:
CAtWork05.exe
(185 KB)
Browse the Code Online
Q In your April 2003 column you described how to implement a class called CWebVersion that checks the software version against a file stored on the Web, in order to prompt the user to upgrade the program if the version is out of date. Your implementation uses FTP to download the file, but the ISP that hosts my site doesn't allow FTP connections unless I log in with a username and password. Can I download the version file as a simple Web page using HTTP instead of FTP?
Mark Simpson
Q In your April 2003 column you described how to implement a class called CWebVersion that checks the software version against a file stored on the Web, in order to prompt the user to upgrade the program if the version is out of date. Your implementation uses FTP to download the file, but the ISP that hosts my site doesn't allow FTP connections unless I log in with a username and password. Can I download the version file as a simple Web page using HTTP instead of FTP?
Mark Simpson
A In case you didn't read the April 2003 issue, CWebVersion is a class I wrote to let you compare your program's version number against a version number stored on the Web. I use it in my TraceWin program to notify users when there's a new version to download.
A In case you didn't read the April 2003 issue, CWebVersion is a class I wrote to let you compare your program's version number against a version number stored on the Web. I use it in my TraceWin program to notify users when there's a new version to download.
Yes, you can use HTTP; and no, you don't have to convert your file. I probably should have used HTTP in my original implementation, since HTTP is more widely supported than FTP. Many Web hosting providers disallow anonymous FTP for security reasons. While FTP is more efficient for transferring files (that's what it was designed for), HTTP is fine for fetching a simple text file.
CWebVersion reads a text file that has four comma-separated numbers: the high- and low-order 32-bit major and minor version numbers. To use it, you can write:
if (CWebVersion::Online()) {
CWebVersion webver("www.mysite.com");
if (webver.ReadVersion("myversion.txt")) {
// dwVersionMS and dwVersionLS now
// hold the version numbers
}
}
The static member function CWebVersion::Online calls ::InternetQueryOption with INTERNET_OPTION_CONNECTED_STATE to see if the computer is currently connected to the Internet. If so, CWebVersion::ReadVersion reads the version file from your Web site. Once you've done that, you can compare the numbers with the ones compiled in your program, usually in your VERSIONINFO or DllGetVersion resource. (See my April 1998 column at microsoft.com/msj/0498/c0498.aspx for details on this.) The original CWebVersion used FTP to fetch the file; this month I've modified it to use HTTP. Reading an HTTP file over the Web is easy with MFC's WinInet classes:
// 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();
This sequence attempts to download the file www.dilascia.com/TraceWinVer.txt. After calling SendRequest you can call CHttpFile::QueryInfoStatusCode to get the status code—for example, 404 for file not found or 200 for status OK (see wininet.h for a complete list of codes)—then call CHttpFile::Read to read the file into your buffer. CWebVersion::ReadVersion does this, then calls scanf to parse the contents from the format "Mhi,Mlo,mhi,mlo" where Mhi, Mlo, mhi, and mlo are the high- and low-order WORDs of the Major and minor version numbers. CWebVersion stores these in CWebVersion::dwMajorVersion and CWebVersion::dwMinorVersion. Figure 1 shows the full source.
 Figure 1 WebVersion
WebVersion.h
WebVersion.h
//////////////////
// This class encapsulates over-the-Web version checking. It expects a
// text version file that contains four numbers separated by commas, the
// same format for FILEVERSION and PRODUCTVERSION in VS_VERSION_INFO.
// ReadVersion reads these numbers into dwVersionMS and dwVersionLS.
// Uses HTTP, not FTP, to fetch the file.
//
class CWebVersion {
protected:
enum { BUFSIZE = 64 };
char m_version[BUFSIZ]; // version number as text
CString m_sServer; // server name (www.mysite.com)
DWORD m_dwStatus; // status code
CString m_sStatus; // status text
public:
DWORD dwMajorVersion; // version number: most-sig 32 bits
DWORD dwMinorVersion; // version number: least-sig 32 bits
CWebVersion(LPCTSTR server) : m_sServer(server) { }
~CWebVersion() { }
static BOOL Online();
BOOL ReadVersion(LPCTSTR lpFileName);
LPCSTR GetVersionText() { return m_version; }
DWORD GetStatus() { return m_dwStatus; }
CString GetStatusText() { return m_sStatus; }
};
WebVersion.cpp
WebVersion.cpp
#include „stdafx.h"
#include „WebVersion.h"
//////////////////
// Check if connected to Internet.
//
BOOL CWebVersion::Online()
{
DWORD dwState = 0;
DWORD dwSize = sizeof(DWORD);
return InternetQueryOption(NULL,
INTERNET_OPTION_CONNECTED_STATE, &dwState, &dwSize)
&& (dwState & INTERNET_STATE_CONNECTED);
}
//////////////////
// Read version number as string into buffer. Use MFC CHttpFile.
//
BOOL CWebVersion::ReadVersion(LPCTSTR lpFileName)
{
ASSERT(lpFileName);
BOOL bRet = FALSE; // assume failure
m_dwStatus=0;
m_sStatus.Empty();
// Read version file using MFC Inet classes.
INTERNET_PORT nPort = INTERNET_DEFAULT_HTTP_PORT;
CInternetSession session(_T("MySession"));
CHttpConnection* pConn = NULL;
CHttpFile* pFile = NULL;
try {
pConn = session.GetHttpConnection(m_sServer, nPort);
pFile = pConn->OpenRequest(
CHttpConnection::HTTP_VERB_GET, lpFileName);
pFile->SendRequest();
pFile->QueryInfoStatusCode(m_dwStatus);
if (m_dwStatus==HTTP_STATUS_OK) {
const UINT BUFSIZE = 128;
UINT nRead = pFile->Read(m_version, BUFSIZE);
if (nRead>0) {
// read version number in the form Mhi,Mlo,mhi,mlo
m_version[nRead] = 0;
int Mhi,Mlo,mhi,mlo;
_stscanf(m_version, (_T(«%x,%x,%x,%x»)),
&Mhi, &Mlo, &mhi, &mlo);
dwMajorVersion = MAKELONG(Mlo,Mhi);
dwMinorVersion = MAKELONG(mlo,mhi);
bRet = TRUE; // success!
}
} else {
pFile->QueryInfo(HTTP_QUERY_STATUS_TEXT, m_sStatus);
}
} catch (CInternetException* pEx) { ... }
session.Close();
delete pFile;
delete pConn;
return bRet;
}
To test CWebVersion, I wrote a program called GetVersion.exe (see Figure 2). When I first tested CWebVersion on my own Web site, I named my version file TraceWinVer.dat. When I tried to download it, I got error 404 (file not found) even though the file was there. At first I thought perhaps I should add .dat to the list of accepted file types in the request header:
static LPCTSTR MyHeaders = _T("Accept: text/dat\r\n");
...
pHttpFile->AddRequestHeaders(MyHeaders);
Figure 2 A Little Test Program
However, this didn't fix the problem. I still got error 404. Well, it turns out my hosting provider had their server configured to prevent browsing .dat extensions. They were happy to change this, but I opted instead to keep the security and rename my version file TraceWinVer.txt. After all, it's text.
If you're programming with the Microsoft ® .NET Framework, you can use HttpWebRequest and HttpWebResponse to fetch a file using HTTP, instead of MFC. With the .NET Framework, you create an HttpWebRequest with the full URL, then call GetResponse to send the request and fetch the response:
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());
The dynamic casts are kind of clunky, but necessary to access the HTTP-specific properties and methods of HttpWebRequest and HttpWebResponse. If you're using Visual Studio ® 2005 with C++/CLI, replace the pointers with tracking handles (^) and you don't need the S for managed string literals. To read the contents of the file in .NET, create a StreamReader on the response stream and read it. In code, it looks like this:
StreamReader* strm =
new StreamReader(resp->GetResponseStream(), encoding);
String* content = strm->ReadToEnd();
strm->Close();
For .NET-heads, I wrote a full GetVersion program in managed C++. You can download it from the MSDN®Magazine Web site.
Q How can I add any sound (not only with the MessageBeep function) to an MFC-based app?
Q How can I add any sound (not only with the MessageBeep function) to an MFC-based app?
Alexander Potapenko
A It's not hard to add sounds to your MFC-based app, but before I show you how, let me remind you that, especially in software, silence is golden. While there are many situations where sound is appropriate (your build failed, e-mail arrived, It's time to buy groceries), in most cases, It's better to keep quiet. Software sounds are the programming equivalent of belching. Have you ever visited one of those Web sites with the home page that plays a little jingle? The first thing most people do is press the Back button. If you must add sounds, please don't forget to provide a Mute option in your program's settings, under Tools | Options.
A It's not hard to add sounds to your MFC-based app, but before I show you how, let me remind you that, especially in software, silence is golden. While there are many situations where sound is appropriate (your build failed, e-mail arrived, It's time to buy groceries), in most cases, It's better to keep quiet. Software sounds are the programming equivalent of belching. Have you ever visited one of those Web sites with the home page that plays a little jingle? The first thing most people do is press the Back button. If you must add sounds, please don't forget to provide a Mute option in your program's settings, under Tools | Options.
OK, now that I'm off my soapbox I can tell you about a Windows ® function called PlaySound that does what you want. It's defined in <mmsystem.h> and you have to link with winmm.lib. PlaySound takes the name of a sound file or resource and plays the sound. The following shows an example:
PlaySound("woofwoof.wav",NULL,SND_NODEFAULT);
Here the special flag SND_NODEFAULT tells Windows: don't play the default sound (MessageBeep) if you can't find the file. Figure 3 shows the other flags. As with so many Windows functions, there are many different ways to use PlaySound, many of them poorly documented. In fact, some flags continue to mystify me. Never fear, I'll shine what light I can into this fog.
 Figure 3 PlaySound Flags
| Flag |
Description |
| SND_APPLICATION |
Play app-specific sound. (Read from registry key HKCU\AppEvents\Schemes\Apps\progname). |
| SND_ALIAS |
The pszSound parameter is a system-event alias in the registry or the WIN.INI file. |
| SND_ALIAS_ID |
The pszSound parameter is a resource ID. |
| SND_ASYNC |
Play sound asynchronously. PlaySound returns immediately after launching the sound. Call PlaySound(NULL) to stop. |
| SND_FILENAME |
Play a sound file; pszSound is the file name. |
| SND_LOOP |
Play sound repeatedly until PlaySound is called again with the pszSound parameter set to NULL. You must also use SND_ASYNC to indicate an asynchronous sound event. |
| SND_MEMORY |
Play sound image in memory. |
| SND_NODEFAULT |
Don't play default sound if the requested sound can't be found. |
| SND_NOSTOP |
Don't stop the current sound already playing. If this flag is not specified, PlaySound attempts to stop the currently playing sound so that the device can be used to play the new sound. |
| SND_NOWAIT |
If the driver is busy, return immediately without playing the sound. |
| SND_RESOURCE |
Play a sound (WAVE) resource. |
One of the most useful ways to play a sound is with the SND_APPLICATION flag, which plays an application-associated sound. For example:
PlaySound("AppExit",NULL, SND_APPLICATION|SND_NODEFAULT);
Figure 4 Happy Sounds
This plays your AppExit sound. And what exactly is that? Windows looks in the registry for HKCU\AppEvents\Schemes\Apps\AppExit and reads the value for .current. If the .current value is a file name like exit.wav, Windows plays it. Windows searches the current directory, the Windows directory, the Windows system directory, and your PATH environment variable. Why are app-associated sounds cool? Because users can customize them with the Control Panel sound applet (see Figure 4). PlaySound also has a SND_RESOURCE flag that lets you play sounds from your resource file. To play a resource sound, you must first add the sound to your resource (.rc) file:
AppExit WAVE "res\\STExit.wav"
Note the resource must be a WAVE resource—a critical detail that isn't documented, as far as I can tell. The resource compiler embeds the WAV file in your EXE so now you can use SND_RESOURCE to play it, like so:
PlaySound("AppExit", AfxGetResourceHandle(), SND_RESOURCE);
PlaySound needs the module handle for the module that contains the resource, which you can get by calling AfxGetResourceHandle (which for most apps is the same as AfxGetInstanceHandle). In the previous snippets, the resource identifier is a string ("AppExit"), but you can also use an integer ID if you specify SND_ALIAS_ID.
To simplify your life, I wrote a little class, CSoundMgr, that makes implementing app sounds as easy as eating cherries. CSoundMgr lets you define logical sounds and play them by ID. It has functions to register your sounds for you, and lets you mute your app in one fell swoop by changing a flag. CSoundManager even searches for default sounds in your resource file. To show how it works in practice, I wrote a test program called—what else?—SoundTest. SoundTest is a typical MFC dialog-based app. It displays the current values for five application sounds (see Figure 5).
Figure 5 SoundTest
The first step to define sounds for SoundTest is to create IDs for the sounds. I used an enum with five values: MYSND_HAPPY, MYSND_UNHAPPY, and so on. Don't use zero for any sound ID; CSoundMgr::PlaySound(0) stops the current sound, equivalent to ::PlaySound(NULL, NULL, 0). With IDs defined, you can use the macros defined in SoundMgr.h to build a sound table, like so:
BEGIN_SOUND_MAP(MySounds)
DEFINE_SOUND(MYSND_HAPPY, _T("ST_Happy"), _T("SoundTest Happy"))
...
END_SOUND_MAP()
Each table entry holds three items: an ID, a logical name, and a GUI name. The ID is used to play the sound. The logical name (for example, ST_Happy) is the name used internally for the registry keys and default sound resources. The GUI name is the human-readable name users see when they use the Sounds control panel applet—for example, "SoundTest Happy" as in Figure 4. Once you've defined your table, the next step is to create a CSoundMgr initialized from your table:
// THE sound manager for this app
CSoundMgr SoundMgr(MySounds);
You only need one CSoundMgr for the entire app. Finally, if you want default built-in sounds, you must add a WAVE resource for each logical sound name that has a default. For example:
ST_HAPPY WAVE "res\\STHappy.wav"
ST_UNHAPPY WAVE "res\\STUnhappy.wav"
Now with the sounds defined, all you have to do to play a sound is write the following:
SoundMgr.PlaySound(MYSND_HAPPY)
And CSoundMgr plays the happy sound. CSoundMgr looks first in the registry for HKCU\AppEvents\Schemes\Apps\SoundTest\ST_Happy\.current; if there's no such registry key/value, CSoundMgr::PlaySound plays the sound resource with the same logical name, ST_Happy. You can call CSoundMgr::IsRegistered to see if your sounds are registered—if not, call CSoundMgr::Register to register them, as shown here:
if (!SoundMgr.IsRegistered())
SoundMgr.Register();
CSoundMgr::Register creates all the registry keys required for users to customize sounds in the Sounds control panel applet. It doesn't actually set any of the key values, but rather leaves them empty so CSoundMgr::PlaySound will use the default resource sounds. If you don't want default sounds, don't create resources for them, or use the following:
CSoundMgr::m_bUseResourceSounds =
FALSE;
Implementing CSoundMgr is fairly straightforward. Most of the code is spent groveling through the registry, perhaps one of the most onerous chores in all of Windows-based programming (see Figure 6). Lucky for you I've already done the work. The subkeys for each logical sound are in HKCU\AppEvents\Schemes\Apps\progname, where progname is your program's name, the same string you use for program Settings, the one returned by ::AfxGetAppName. Each subkey holds the sound file name in the .current value. CSoundMgr doesn't create the values, only the keys, since the Sounds Control Panel applet creates a .current value when the user changes a sound. The default value for each logical sound key is the human-readable GUI name for the sound, but Windows ignores this value as far as I can tell; it looks for GUI names in a different key, HKCU\AppEvents\EventNames. Go figure.
 Figure 6 SoundMgr
SoundMgr.h
#pragma once
// one of these for each application sound
struct APPSOUND {
UINT id; // ID for programming
LPCTSTR log_name; // logical event name for registry
LPCTSTR gui_name; // user-visible event name
};
//////////////////
// Class to manage app sounds. Use macros to create a static table, then
// construct one of these initialized from the table.
//
class CSoundMgr {
protected:
APPSOUND* m_soundtab; // table of APPSOUNDs
// helper fns
BOOL GetSoundFileName(LPCTSTR logname, CString& s);
BOOL GetLogicalName(UINT id, CString& logname);
public:
BOOL m_bUseResourceSounds; // look for default sounds in res
file?
BOOL m_bEnableSounds; // enable/disable sound
CSoundMgr(APPSOUND* tab);
~CSoundMgr() { }
CString GetSoundSchemeKey(LPCTSTR logname=NULL);
BOOL PlaySound(UINT id, DWORD dwAddFlags=0);
BOOL PlaySound(LPCTSTR logname, DWORD dwAddFlags=0);
BOOL IsRegistered(); // are sounds registered?
BOOL Register(); // register sounds
};
//////////////////
// Macros to build sounds table.
//
#define BEGIN_SOUND_MAP(name) APPSOUND name[] = {
#define DEFINE_SOUND(id, regname, guiname) { id, regname, guiname },
#define END_SOUND_MAP() { 0, NULL, NULL } };
SoundMgr.cpp
#include "stdafx.h"
#include "SoundMgr.h"
//////////////////
// Construct sound manager from table.
//
CSoundMgr::CSoundMgr(APPSOUND* tab) : m_soundtab(tab)
{
m_bUseResourceSounds = TRUE; // default: use default resource sounds
m_bEnableSounds = TRUE; // default: sounds enabled
}
//////////////////
// Get name of registry key for sound events:
// HKCU\AppEvents\Schemes\Apps\logname, where logname is optional.
//
CString CSoundMgr::GetSoundSchemeKey(LPCTSTR logname)
{
CString key;
key.Format(_T("AppEvents\\Schemes\\Apps\\%s"), AfxGetAppName());
if (logname) {
key += _T("\\");
key += logname;
}
return key;
}
SoundMgr.h
#pragma once
// one of these for each application sound
struct APPSOUND {
UINT id; // ID for programming
LPCTSTR log_name; // logical event name for registry
LPCTSTR gui_name; // user-visible event name
};
//////////////////
// Class to manage app sounds. Use macros to create a static table, then
// construct one of these initialized from the table.
//
class CSoundMgr {
protected:
APPSOUND* m_soundtab; // table of APPSOUNDs
// helper fns
BOOL GetSoundFileName(LPCTSTR logname, CString& s);
BOOL GetLogicalName(UINT id, CString& logname);
public:
BOOL m_bUseResourceSounds; // look for default sounds in res
file?
BOOL m_bEnableSounds; // enable/disable sound
CSoundMgr(APPSOUND* tab);
~CSoundMgr() { }
CString GetSoundSchemeKey(LPCTSTR logname=NULL);
BOOL PlaySound(UINT id, DWORD dwAddFlags=0);
BOOL PlaySound(LPCTSTR logname, DWORD dwAddFlags=0);
BOOL IsRegistered(); // are sounds registered?
BOOL Register(); // register sounds
};
//////////////////
// Macros to build sounds table.
//
#define BEGIN_SOUND_MAP(name) APPSOUND name[] = {
#define DEFINE_SOUND(id, regname, guiname) { id, regname, guiname },
#define END_SOUND_MAP() { 0, NULL, NULL } };
SoundMgr.cpp
#include "stdafx.h"
#include "SoundMgr.h"
//////////////////
// Construct sound manager from table.
//
CSoundMgr::CSoundMgr(APPSOUND* tab) : m_soundtab(tab)
{
m_bUseResourceSounds = TRUE; // default: use default resource sounds
m_bEnableSounds = TRUE; // default: sounds enabled
}
//////////////////
// Get name of registry key for sound events:
// HKCU\AppEvents\Schemes\Apps\logname, where logname is optional.
//
CString CSoundMgr::GetSoundSchemeKey(LPCTSTR logname)
{
CString key;
key.Format(_T("AppEvents\\Schemes\\Apps\\%s"), AfxGetAppName());
if (logname) {
key += _T("\\");
key += logname;
}
return key;
}
//////////////////
// Play logical sound from ID.
//
BOOL CSoundMgr::PlaySound(UINT id, DWORD dwAddFlags)
{
if (id==0) {
::PlaySound(NULL,NULL,0); // ID 0 = stop playing async sound
return TRUE;
}
CString logname;
return GetLogicalName(id, logname) ?
PlaySound(logname, dwAddFlags) : FALSE;
}
//////////////////
// Play logical sound using logical name.
//
BOOL CSoundMgr::PlaySound(LPCTSTR logname, DWORD dwAddFlags)
{
if (m_bEnableSounds) {
CString soundname;
if (GetSoundFileName(logname, soundname) && !soundname.IsEmpty()) {
return ::PlaySound(soundname, NULL,
dwAddFlags|SND_APPLICATION|SND_NODEFAULT);
} else if (m_bUseResourceSounds) {
// play sound resource w/same logical name
return ::PlaySound(logname, AfxGetResourceHandle(),
dwAddFlags|SND_RESOURCE|SND_NODEFAULT);
}
}
return FALSE;
}
//////////////////
// Get logical sound name from ID. Search table for sound that matches.
//
BOOL CSoundMgr::GetLogicalName(UINT id, CString& logname)
{
if (m_soundtab) {
for (int i=0; m_soundtab[i].id; i++) {
if (m_soundtab[i].id==id) {
logname = m_soundtab[i].log_name;
return TRUE;
}
}
}
return FALSE;
}
//////////////////
// Read current sound file name from registry.
//
BOOL CSoundMgr::GetSoundFileName(LPCTSTR logname, CString& s)
{
CString key = GetSoundSchemeKey(logname); // get registry key
// open key and read the ".current" value.
BOOL bRet = FALSE;
HKEY hkey;
if (RegOpenKey(HKEY_CURRENT_USER, key, &hkey)==ERROR_SUCCESS) {
TCHAR val[255]={0};
LONG vlen=sizeof(val)/sizeof(TCHAR);
if (RegQueryValue(hkey, _T(".current"), val, &vlen)==ERROR_SUCCESS)
{
s = val; // return in caller’s string
bRet = TRUE; // success
}
RegCloseKey(hkey);
}
return bRet; // failed :(
}
//////////////////
// Test if sounds are registered. Algorithm: see if first logical sound is
// registered. Note this is not foolproof if someone deletes other keys.
//
BOOL CSoundMgr::IsRegistered()
{
ASSERT(m_soundtab);
CString key = GetSoundSchemeKey(m_soundtab[1].log_name);
HKEY hkey;
if (RegOpenKey(HKEY_CURRENT_USER, key, &hkey)==ERROR_SUCCESS)
return TRUE;
RegCloseKey(hkey);
return FALSE;
}
// Helper function to open a registry key, creating if necessary
static LONG RegCreateOpenKey(HKEY hkey, LPCTSTR lpSubKey, PHKEY phkRes)
{
return RegOpenKey(hkey, lpSubKey, phkRes)==ERROR_SUCCESS ?
ERROR_SUCCESS : RegCreateKey(hkey, lpSubKey, phkRes);
}
//////////////////
// Register app-defined sounds. Note this only creates all the keys,
// it does NOT set the values (file names) since the app will play default
// sounds from the resource file. Creating the keys is all that’s required
// to let users change the sounds.
//
BOOL CSoundMgr::Register()
{
ASSERT(m_soundtab);
BOOL bRet = FALSE;
static LPCTSTR APPS = _T("AppEvents\\Schemes\\Apps");
static LPCTSTR LABELS = _T("AppEvents\\EventLabels");
HKEY hkApps,hkLabels;
if (RegOpenKey(HKEY_CURRENT_USER, APPS, &hkApps)==ERROR_SUCCESS) {
if (RegOpenKey(HKEY_CURRENT_USER, LABELS, &hkLabels)==ERROR_SUCCESS)
{
HKEY hkApp;
if (RegCreateOpenKey(hkApps, AfxGetAppName(),
&hkApp)==ERROR_SUCCESS) {
// opened event key, now add all the sounds
for (int i=0; m_soundtab[i].id; i++) {
HKEY hk;
LPCTSTR lpName = m_soundtab[i].gui_name;
DWORD dwLen = (DWORD)_tcslen(m_soundtab[i].gui_name);
// add app event
RegCreateKey(hkApp, m_soundtab[i].log_name, &hk);
RegSetValue(hk, NULL, REG_SZ, lpName, dwLen);
RegCloseKey(hk);
// add event name
RegCreateKey(hkLabels, m_soundtab[i].log_name, &hk);
RegSetValue(hk,NULL,REG_SZ, lpName, dwLen);
RegCloseKey(hk);
}
RegCloseKey(hkApp);
bRet = TRUE; // success! (maybe)
}
RegCloseKey(hkLabels);
}
RegCloseKey(hkApps);
}
return bRet;
}
So you have to create another set of keys, one for each logical sound name, whose default value is the human-readable GUI name. Of course, if you use CSoundMgr, you can forget all about registry keys. Just define your sounds and call CSoundMgr::Register. If the user has customized sounds, calling Register won't clobber them since it only creates those keys that don't already exist. If you want to implement a reset command to restore sounds to their defaults, you should delete HKCU\AppEvents\Schemes\Apps\progname and then call Register again.
One last bit of advice: in choosing your logical names, be careful to avoid collisions with other apps. Unfortunately, all the event names live in the same namespace within HKCU\AppEvents\EventNames, so I recommend using a prefix like I did for SoundTest, where all the logical names begin with ST_. It makes them easier to find too, since REGEDT32 lists the registry keys in alphabetical order.
SoundTest has an Enable Sounds checkbox that lets users turn sound on or off. The command handler for this button toggles the variable CSoundMgr::m_bEnableSounds. I leave you to download and ponder the source for full details. Happy programming!
Send your questions and comments for Paul to cppqa@microsoft.com.
Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available from his Web site, www.dilascia.com.
|
|