65.9K
CodeProject 正在变化。 阅读更多。
Home

枚举系统代码页

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (8投票s)

2006 年 12 月 4 日

CPOL

5分钟阅读

viewsIcon

37624

downloadIcon

1008

用于 EnumSystemCodePages API 调用的 MFC 和 ATL 包装类

Sample Image - codepage.png

引言

最近我需要一些代码来枚举系统中安装的代码页,并偶然发现了 EnumSystemCodePages API 调用,它正如其名(您可以枚举已安装的代码页以及 Windows 支持的代码页)。然而,我发现这个 API 调用与其他 Enum... 类型调用的工作方式略有不同,这可能会给多线程应用程序带来问题(稍后会详细介绍)。

因此,为了解决这个问题,我创建了一个特殊的 CCodePage 类,您可以在您的 MFC/ATL/WTL/STL 应用程序中使用它来轻松地

  • 枚举已安装的代码页
  • 枚举系统代码页
  • 获取特定代码页的名称

为了尽可能满足所有人的需求,该类有三个独立版本

  • 一个 **MFC** 版本,适用于 MFC42 (Visual Studio 6) 和 MFC8 (Visual Studio 2005)
  • 一个 **ATL** 版本,适用于 Visual Studio 6 和 Visual Studio 2005。此版本也可在两种环境下与 WTL 应用程序一起使用。
  • 一个 **STL** 兼容版本(注意:此版本依赖 Boost 库的一个组件,将单独介绍。)

Using the Code

要在您的应用程序中使用此代码,请先包含相应的头文件

MFC 版本

#include "CodePageMFC.h"

ATL 版本

#include "CodePageATL.h"

接下来,创建 CCodePage 类的实例 - 请注意,提供的示例代码属于 rec 命名空间

rec::CCodePage codepages;

或者,如果您愿意

using namespace rec;
...
CCodePage codepages;

接下来您需要决定是需要实际安装的代码页列表,还是您的 Windows 版本支持的代码页列表。为此,CCodePage 类公开了以下两个方法,它们都返回一个指向 CCodePage::CCodePageObjectconst 引用。例如

using namespace rec;
CCodePage codepages;
const CCodePage::CCodePageObject installed = codepage.GetInstalled();
...
const CCodePage::CCodePageObject supported = codepage.GetSupported();

CCodePageObject 类支持以下 public 方法

  • GetCount:返回可用代码页的数量
  • operator[index]:返回特定索引处的代码页 ID
  • IsEmpty:如果对象为空(即没有可用的代码页),则返回 true
  • GetName(index):返回特定索引处代码页的名称

CCodePageObject 被设计成可以像数组一样使用,所以,例如,要在 MFC 应用程序中输出所有已安装代码页的 ID 和名称,只需这样做

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));
}

非常简单。

EnumSystemCodePages

枚举代码页并不复杂 - 您只需调用 EnumSystemCodePages API 函数。但是,与我遇到的几乎所有其他 Enum... 函数不同,您无法将 lParam 传递给回调函数。这有什么问题?嗯,回调函数只接受一个参数 - 以 string 形式的代码页 ID

BOOL CALLBACK EnumCodePagesProc(LPTSTR lpCodePageString)

这意味着,如果您想对 lpCodePageString 做一些有用的事情 - 例如将 string 添加到容器中 - 您需要使用一些静态/全局变量,这可能导致线程安全问题。例如,如果您的 EnumCodePagesProclpCodePageString 添加到一个全局容器中,当两个线程同时调用此函数时会发生什么?

为了解决这个问题,我使用了一个 CRITICAL_SECTION 来确保 EnumCodePagesProc 函数无法同时执行。MFC 版本使用 CCriticalSectionCSingleLock 对象组合,而 ATL 版本使用 CComAutoCriticalSection

理想情况下,EnumSystemCodePages 函数会接受一个 lParam,然后该 lParam 会被传递给回调函数。例如,这个 lParam 可以是一个合适的容器的地址 - 从而完全避免了线程问题。当然,其他 Enum... 函数似乎也是这样工作的。

请注意,代码页在您第一次调用任何 Get... 函数之前不会被实际枚举。

幕后

这里显示了 MFC 头文件

#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(); 
 };
}

MFC 源文件

#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;
 }  
}

示例应用程序

包括了各种风味的示例应用程序,但它们都做同样的事情 - 显示一个对话框,其中包含已安装和支持的代码页列表。zip 文件中包含发布版本。

Windows 95/NT4

请注意,GetCPInfoEx 函数(此处用于获取代码页名称)在 Win95/NT4 上不受支持,您会注意到 WINVERstdafx.h 中相应地设置为 0x0410。但是,如果您想在必须在这些平台之一上运行的应用程序中使用此代码,那么您可以使用以下 rec::CCodePage::CCodePageObject::GetText 的替代方法动态查找 GetCPInfoEx 函数

// 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("");
}

STL/Boost 版本

源文件下载中还包含一个 STL 版本。请注意,按照 STL 风格的命名传统,该类已被重命名为 rec::code_page

STL 版本使用 std::vector 作为存储代码页 ID 的容器,并且为了线程安全,它结合了 boost::mutexboost::mutex::scoped_lock。这个版本应该谨慎对待 - 我添加它是为了完整性。

Unicode

所有版本的代码都兼容 Unicode,所以您可以随意编译 Unicode 或 MBCS。我没有在 98/ME 上使用 UnicoWS 库测试过此代码,但根据 MSDN,它应该可以工作。

哦,STL 版本是 Unicode 专用的(通过 std::wstring),但您可以使用一个 TCHAR 兼容的 string 轻松地更改它(CodeProject 上有很多这方面的例子)。

顺便说一句,我目前只使用 Unicode,如果确实需要 Win9x 支持,则链接 UnicoWS。

空白代码页名称

请注意,在请求支持的代码页名称时,它可能会返回为空,因为该代码页不一定存在于您的系统中。然而,我注意到有两个我安装的代码页名称丢失了 - **20949** 和 **28603**,这是一个谜(我运行的是 XP SP2)。

Possible Enhancements

解决这个问题可能还有很多其他方法(例如,EnumSystemCodePages 的回调函数似乎实现得不太好),所以如果您能想到改进此代码的方法,请告诉我。

© . All rights reserved.