枚举系统代码页






4.63/5 (8投票s)
用于 EnumSystemCodePages API 调用的 MFC 和 ATL 包装类

引言
最近我需要一些代码来枚举系统中安装的代码页,并偶然发现了 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::CCodePageObject
的 const
引用。例如
using namespace rec;
CCodePage codepages;
const CCodePage::CCodePageObject installed = codepage.GetInstalled();
...
const CCodePage::CCodePageObject supported = codepage.GetSupported();
CCodePageObject
类支持以下 public
方法
GetCount
:返回可用代码页的数量operator[index]
:返回特定索引处的代码页 IDIsEmpty
:如果对象为空(即没有可用的代码页),则返回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
添加到容器中 - 您需要使用一些静态/全局变量,这可能导致线程安全问题。例如,如果您的 EnumCodePagesProc
将 lpCodePageString
添加到一个全局容器中,当两个线程同时调用此函数时会发生什么?
为了解决这个问题,我使用了一个 CRITICAL_SECTION
来确保 EnumCodePagesProc
函数无法同时执行。MFC 版本使用 CCriticalSection
和 CSingleLock
对象组合,而 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 上不受支持,您会注意到 WINVER
在 stdafx.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::mutex
和 boost::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
的回调函数似乎实现得不太好),所以如果您能想到改进此代码的方法,请告诉我。