Introduction
Recently I needed some code that would enumerate the installed code pages on a system, and came across the EnumSystemCodePages
API call, which does exactly what it says (you can enumerate both installed code pages, and those that Windows actually support). However, I found that this API call works slightly differently to other Enum...
type calls, which can potentially cause problems with multi-threaded applications (more on this later).
So, to address this I have created a special CCodePage
class, which you can use in your MFC/ATL/WTL/STL applications to easily:
- Enumerate installed code pages
- Enumerate system code pages
- Fetch the name of a specific code page
In order to please as many people as possible, there are three separate versions of the class:
- An MFC version which will work with both MFC42 (Visual Studio 6) and MFC8 (Visual Studio 2005)
- An ATL version that works with both Visual Studio 6 and Visual Studio 2005. This version can also be used with WTL application in both environments.
- An STL-friendly version (Note: This version relies on a component of the Boost library and will be covered separately.)
Using the Code
To use this code in your application, first include the appropriate header:
MFC Version
#include "CodePageMFC.h"
ATL Version
#include "CodePageATL.h"
Next, create an instance of the CCodePage
class - note that the sample code supplied belongs to the rec
namespace:
rec::CCodePage codepages;
or, if you prefer:
using namespace rec;
...
CCodePage codepages;
Next you need to decide if you want a list of code pages that are actually installed, or those that are supported by your Windows version. To this end, the CCodePage
class exposes the following two methods, both of which return a const
reference to a CCodePage::CCodePageObject.
For example:
using namespace rec;
CCodePage codepages;
const CCodePage::CCodePageObject installed = codepage.GetInstalled();
...
const CCodePage::CCodePageObject supported = codepage.GetSupported();
The CCodePageObject
class supports the following public
methods:
GetCount
: Returns the number of code pages available operator[index]
: Returns a code page ID at a specific indexIsEmpty
: Returns true
if the object is empty (i.e. no code pages are available)GetName(index)
: Returns the name of a code page at a specific index
A CCodePageObject
is designed to be treated like an array, so, for example, to output the ID
and name of all the installed code pages in an MFC application, simply do the following:
using namespace rec;
CCodePage codepages;
const CCodePage::CCodePageObject installed = codepages.GetInstalled();
for (int i = 0; i < installed.GetCount(); i++)
{
TRACE(_T("ID: %d, name: %s\n"), installed[i], installed.GetName(i));
}
Pretty simple.
EnumSystemCodePages
Enumerating code pages isn't rocket science - you simply call the EnumSystemCodePages
API function. However, unlike almost every other Enum...
function I have come across, you cannot pass a lParam
to the callback function. Why is this a problem? Well, the callback function simply takes a single parameter - the code page ID in string
form:
BOOL CALLBACK EnumCodePagesProc(LPTSTR lpCodePageString)
Now, this means that if you want to do something useful with lpCodePageString
- such as add the string
to a container - you need to use some static/global variables, which means you could end up with some thread safety problems. For example, if your EnumCodePagesProc
was adding the lpCodePageString
to a global container, what would happen if two threads were calling this function simultaneously?
To solve this, I use a CRITICAL_SECTION
to ensure that the EnumCodePagesProc
function cannot execute. The MFC version uses a combination of a CCriticalSection
and a CSingleLock
object, and the ATL version uses a CComAutoCriticalSection
.
Ideally, the EnumSystemCodePages
function would take a lParam
that would then be passed to the callback function. This lParam
could be, for example, the address of a suitable container - avoiding the threading issue altogether. Certainly this is how other Enum...
functions seem to operate.
Note that the code pages are not actually enumerated until you call one of the Get...
functions for the first time.
Under the Hood
The MFC header is shown here:
#pragma once
#include <afxtempl.h>
#include <afxmt.h>
namespace rec
{
class CCodePage
{
private:
typedef CArray<UINT, UINT> CodePageContainer;
public:
class CCodePageObject
{
private:
CodePageContainer s_arrCodePages; static CCriticalSection s_critSec; static CCodePageObject* s_pCurrent; public:
int GetCount() const;
int operator[](int nIndex) const;
bool IsEmpty() const;
CString GetName(int nIndex) const;
static void EnumerateCodePages(CCodePageObject* pObject);
private:
void Add(UINT nCodePage);
static BOOL CALLBACK EnumCodePagesProc(LPTSTR lpCodePageString);
virtual DWORD GetFlags() const = 0;
};
class CInstalled : public CCodePageObject
{
private:
virtual DWORD GetFlags() const { return CP_INSTALLED; }
};
class CSupported : public CCodePageObject
{
private:
virtual DWORD GetFlags() const { return CP_SUPPORTED; }
};
private:
CInstalled m_cpInstalled; CSupported m_cpSupported; public:
const CCodePageObject& GetInstalled();
const CCodePageObject& GetSupported();
};
}
The MFC source:
#include "stdafx.h"
#include "CodePageMFC.h"
namespace rec
{
CCriticalSection CCodePage::CCodePageObject::s_critSec;
CCodePage::CCodePageObject* CCodePage::CCodePageObject::s_pCurrent = NULL;
int CCodePage::CCodePageObject::GetCount() const
{
return static_cast<int>(s_arrCodePages.GetSize());
}
int CCodePage::CCodePageObject::operator[](int nIndex) const
{
return s_arrCodePages[nIndex];
}
bool CCodePage::CCodePageObject::IsEmpty() const
{
return s_arrCodePages.GetSize() == 0;
}
void CCodePage::CCodePageObject::Add(UINT nCodePage)
{
s_arrCodePages.Add(nCodePage);
}
CString CCodePage::CCodePageObject::GetName(int nIndex) const
{
#if (WINVER >= 0x410)
CPINFOEX cpInfoEx = { 0 };
GetCPInfoEx(s_arrCodePages[nIndex], 0, &cpInfoEx);
return cpInfoEx.CodePageName;
#else
return _T("");
#endif
}
void CCodePage::CCodePageObject::EnumerateCodePages(CCodePageObject* pObject)
{
ASSERT(pObject != NULL);
CSingleLock lock(&s_critSec, TRUE);
s_pCurrent = pObject;
::EnumSystemCodePages(EnumCodePagesProc, pObject->GetFlags());
}
BOOL CALLBACK CCodePage::CCodePageObject::EnumCodePagesProc(LPTSTR lpCodePageString)
{
ASSERT(s_pCurrent != NULL);
s_pCurrent->Add(_ttoi(lpCodePageString));
return TRUE;
}
const CCodePage::CCodePageObject& CCodePage::GetInstalled()
{
if (m_cpInstalled.IsEmpty())
CCodePageObject::EnumerateCodePages(&m_cpInstalled);
return m_cpInstalled;
}
const CCodePage::CCodePageObject& CCodePage::GetSupported()
{
if (m_cpSupported.IsEmpty())
CCodePageObject::EnumerateCodePages(&m_cpSupported);
return m_cpSupported;
}
}
Sample Applications
Various flavour sample applications are included, but they all do the same thing - display a dialog box containing lists of installed and supported code pages. Release builds are included in the zip file.
Windows 95/NT4
Note that the GetCPInfoEx
function (used here to obtain the code page name) isn't supported on Win95/NT4, and you will note that WINVER
is set to 0x0410
in stdafx.h accordingly. However, if you want to use this code in an application that has to run on either of these platforms, then you can dynamically look for the GetCPInfoEx
function using the following replacement for rec::CCodePage::CCodePageObject::GetText
:
CString CCodePage::CCodePageObject::GetName(int nIndex) const
{
typedef BOOL (WINAPI *GETCPINFOEX_FN)(UINT, DWORD, LPCPINFOEX);
GETCPINFOEX_FN pGetCPInfoEx = NULL;
HMODULE hKernel = GetModuleHandle(_T("Kernel32.dll"));
if (hKernel != NULL)
{
#ifdef _UNICODE
pGetCPInfoEx =
reinterpret_cast<GETCPINFOEX_FN>(::GetProcAddress(kernel, "GetCPInfoExW"));
#else
pGetCPInfoEx =
reinterpret_cast<GETCPINFOEX_FN>(::GetProcAddress(kernel, "GetCPInfoExA"));
#endif
if (pGetCPInfoEx != NULL)
{
CPINFOEX cpInfoEx = { 0 };
if (pGetCPInfoEx(s_arrCodePages[nIndex], 0, &cpInfoEx))
return cpInfoEx.CodePageName;
}
}
return _T("");
}
A version for STL/Boost
Also included in the source file download is an STL version. Note that in the tradition of STL-like names, the class has been renamed as rec::code_page
.
The STL version uses a std::vector
as the container for storing the code page ID
s, and for thread-safety, a boost::mutex
is used in conjunction with a boost::mutex::scoped_lock
. This version should really only be taken with a pinch of salt - I added it for completeness.
Unicode
All versions of the code are Unicode compliant, so feel free to compile for either Unicode or MBCS. I haven't tested the code using the UnicoWS library under 98/ME, but according to the MSDN, it should work.
Oh, the STL version is Unicode only (via std::wstring
), but you could easily change this using a TCHAR
-friendly string
(plenty of examples of this here on CodeProject).
FWIW, I only ever use Unicode nowadays, linking with UnicoWS if Win9x support is absolutely necessary.
Blank Code Page Names
Note that when requesting the name of a supported code page, it may well be returned empty, as the code page isn't necessarily present on your system. However, I noticed that I have two missing names from code pages that are apparently installed - 20949 and 28603 which is a mystery (I am running XP SP2).
Possible Enhancements
There are probably many other ways to approach this problem (i.e. a seemingly poorly implemented callback function for EnumSystemCodePages
), so if you can think of ways in which I can improve this code, then please let me know.