Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / ATL

Enumerating System Code Pages

4.63/5 (8 votes)
4 Dec 2006CPOL5 min read 1   965  
MFC and ATL wrapper classes for the EnumSystemCodePages API call
Sample Image - codepage.png

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

C++
#include "CodePageMFC.h"

ATL Version

C++
#include "CodePageATL.h"

Next, create an instance of the CCodePage class - note that the sample code supplied belongs to the rec namespace:

C++
rec::CCodePage codepages;

or, if you prefer:

C++
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:

C++
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 index
  • IsEmpty: 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:

C++
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:

C++
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:

C++
#pragma once

#include <afxtempl.h>
#include <afxmt.h>

namespace rec
{
 class CCodePage
 {
 private:
  // Code page ID container typedef
  // You can replace this with a container implementation of your choice
  typedef CArray<UINT, UINT> CodePageContainer;
 public:
  class CCodePageObject
  {
  private:
   CodePageContainer s_arrCodePages; // Container of code page IDs
   static CCriticalSection s_critSec; // Critical section used for thread-safety
   static CCodePageObject* s_pCurrent; // Current code page object being filled
  public:
   // Return the number of available code pages
   int GetCount() const;
   // Return a code page at a specific index
   int operator[](int nIndex) const;
   // Returns true if the code page container is empty
   bool IsEmpty() const;
   // Return the name of a code page at a specific index in the container
   CString GetName(int nIndex) const;
   // Enumerate code pages using specific flags
   static void EnumerateCodePages(CCodePageObject* pObject);
  private:
   // Add a new code page to the container
   void Add(UINT nCodePage);
   // EnumSystemCodePages callback function (Installed)
   static BOOL CALLBACK EnumCodePagesProc(LPTSTR lpCodePageString);
   // Pure virtual function that returns the flags to be passed
   // to the EnumSystemCodePages function
   virtual DWORD GetFlags() const = 0;
  };
  
  // Class used to store installed code page IDs
  class CInstalled : public CCodePageObject
  {
  private:
   virtual DWORD GetFlags() const { return CP_INSTALLED; }
  };

  // Class used to store supported code page IDs
  class CSupported : public CCodePageObject
  {
  private:
   virtual DWORD GetFlags() const { return CP_SUPPORTED; }
  };
 private:  
  CInstalled m_cpInstalled; // Installed code pages
  CSupported m_cpSupported; // Supported code pages  
 public:
  // Get the installed code pages
  const CCodePageObject& GetInstalled();
  // Get the supported code pages
  const CCodePageObject& GetSupported(); 
 };
}

The MFC source:

C++
#include "stdafx.h"
#include "CodePageMFC.h"

namespace rec
{
 // Initialize static variables
 CCriticalSection CCodePage::CCodePageObject::s_critSec;
 CCodePage::CCodePageObject* CCodePage::CCodePageObject::s_pCurrent = NULL;

 // Return the number of available code pages
 int CCodePage::CCodePageObject::GetCount() const
 {
  return static_cast<int>(s_arrCodePages.GetSize());
 }

 // Return a code page at a specific index
 int CCodePage::CCodePageObject::operator[](int nIndex) const
 {
  return s_arrCodePages[nIndex];
 }

 // Returns true if the code page container is empty
 bool CCodePage::CCodePageObject::IsEmpty() const
 {
  return s_arrCodePages.GetSize() == 0;
 }

 // Add a new code page to the container
 void CCodePage::CCodePageObject::Add(UINT nCodePage)
 {
  s_arrCodePages.Add(nCodePage);
 }

 // Return the name of a code page at a specific index in the container
 CString CCodePage::CCodePageObject::GetName(int nIndex) const
 {
#if (WINVER >= 0x410)
  CPINFOEX cpInfoEx = { 0 };
  GetCPInfoEx(s_arrCodePages[nIndex], 0, &cpInfoEx);
  return cpInfoEx.CodePageName;
#else
  // GetCPInfoEx not supported on 95/NT4
  return _T("");
#endif
 }

 // Enumerate code pages using specific flags
 void CCodePage::CCodePageObject::EnumerateCodePages(CCodePageObject* pObject)
 {
  ASSERT(pObject != NULL);
  // Lock access to this function to ensure thread safety
  CSingleLock lock(&s_critSec, TRUE);
  s_pCurrent = pObject;
  ::EnumSystemCodePages(EnumCodePagesProc, pObject->GetFlags());
 }

 // EnumSystemCodePages callback function
 BOOL CALLBACK CCodePage::CCodePageObject::EnumCodePagesProc(LPTSTR lpCodePageString)
 {
  ASSERT(s_pCurrent != NULL);
  // Format the code page string as an unsigned int
  // and add to the current code page object
  s_pCurrent->Add(_ttoi(lpCodePageString));
  return TRUE;
 }

 // Get the installed code pages
 const CCodePage::CCodePageObject& CCodePage::GetInstalled()
 {
  if (m_cpInstalled.IsEmpty())
   CCodePageObject::EnumerateCodePages(&m_cpInstalled);
  return m_cpInstalled;
 }

 // Get the supported code pages
 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:

C++
// Return the name of a code page at a specific index in the container
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 IDs, 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)