iconv
这是一个典型的C风格的函数
std::size_t iconv (iconv_t cd、const char* * inbuf、size_t * inbytesleft、
char* * outbuf、size_t * outbytesleft);
使用方法
- 打开一个转换句柄
iconv_t iconv_open = iconv_open(const char* tocode, const char* fromcode);
iconv_t
的底层实际类型是void*
iconv_open
可能带来的错误:
EINVAL
不支持的编码转换ENOMEM
“Out of memory” 内存不足
- 执行转换
std::size_t iconv( iconv cd,
char** inbuf, std::size_t * inbytesleft,
char** outbuf, std::size_t * outbytesleft);
iconv
带来的错误:
E2BIG
Output buffer too small 输出缓冲区内存不够EILSEQ
输入字符串序列有误EINVAL
输入的字符序列不完整
param
inbuf
参数是一个变量的地址,该变量指向输入序列的第一个字符
inbytesleft
表示该缓冲区中的字节数。
outbuf
参数是一个变量的地址,该变量指向输出缓冲区中可用的第一个字节;
outbytesleft
表示输出缓冲区中可用的字节数。
3. 关闭转换句柄
int iconv_close(iconv_t cd);
转换过程
主要的情况是当
inbuf
不为 NULL 且*inbuf
不为NULL
时。在这种情况下,iconv() 函数会将从 *inbuf 开始的多字节序列转换为从*outbuf
开始的多字节序列。最多会读取从*inbuf
开始的*inbytesleft
个字节。最多会写入从*outbuf
开始的 *outbytesleft 个字节。
iconv()
函数一次转换一个多字节字符,对于每个字符转换,它会将 *inbuf
增加,并将 *inbytesleft
减去已转换的输入字节数,将 *outbuf
增加,并将 *outbytesleft
减去已转换的输出字节数,同时更新 cd
中包含的转换状态。如果输入的字符编码是有状态的,iconv()
函数还可以将输入字节序列转换为转换状态的更新而不产生任何输出字节;这种输入称为移位序列。转换可以因五种原因停止:
输入中遇到无效的多字节序列。在本例中,它将errno设置为EILSEQ并返回(size_t) -1。*inbuf左指向无效多字节序列的开头。
遇到一个多字节序列,该序列有效但无法转换为输出的字符编码。这种情况取决于实现和转换描述符。在GNU C库和GNU libiconv中,如果
cd
创建时没有使用后缀//TRANSLIT
或//IGNORE
,则转换是严格的:有损转换会产生这种情况。如果指定了后缀//TRANSLIT
,则在某些情况下可以通过音译避免这种情况。在musl C库中,这种情况不会发生,因为会使用转换为'*'
作为回退。在FreeBSD、NetBSD和Solaris的iconv()
实现中,这种情况也不会发生,因为它们会使用转换为'?'
作为回退。当满足此条件时,iconv()
会将errno
设置为EILSEQ
并返回(size_t) -1
。*inbuf
会指向无法转换的多字节序列的开头。输入的字节序列已经完全转换,即
*inbytesleft
已减少到0。在这种情况下,iconv()
会返回在此次调用期间执行的非可逆转换的次数。在输入中遇到了一个不完整的多字节序列,并且输入字节序列在其后终止。在这种情况下,
iconv()
会将errno
设置为EINVAL
并返回(size_t) -1
。*inbuf
会指向不完整多字节序列的开头。输出缓冲区没有足够的空间来容纳下一个转换后的字符。在这种情况下,
iconv()
会将errno
设置为E2BIG
并返回(size_t) -1
另一种情况是当
inbuf
为NULL
或*inbuf
为NULL
,但outbuf
不为NULL
且*outbuf
不为NULL
时。在这种情况下,iconv()
函数会尝试将cd
的转换状态设置为初始状态,并在*outbuf
处存储相应的移位序列。从*outbuf
开始,最多会写入*outbytesleft
字节。如果输出缓冲区没有足够的空间来存储此重置序列,它会将errno
设置为E2BIG
并返回(size_t) -1
。否则,它会根据写入的字节数增加*outbuf
并减少*outbytesleft
。第三种情况是当
inbuf
为NULL
或*inbuf
为NULL
,且outbuf
为NULL
或*outbuf
为NULL
时。在这种情况下,iconv()
函数会将cd
的转换状态设置为初始状态。iconv()
函数返回在此次调用期间以不可逆方式转换的字符数量;可逆转换不会被计数。如果发生错误,iconv()
会返回(size_t) -1
,并设置errno
以指示具体的错误
注意
在每次调用iconv()
的系列操作中,最后一次调用应将inbuf
或*inbuf
设置为NULL
,以便刷新任何部分转换的输入。
尽管inbuf
和outbuf
的类型为char **
,但这并不意味着它们所指向的对象可以被解释为C字符串或字符数组:字符字节序列的解释由转换函数内部处理。在某些编码中,零字节可能是多字节字符的有效部分。
iconv()
的调用者必须确保传递给函数的指针适合访问相应字符集中的字符。这包括在对齐要求严格的平台上确保正确的对齐。
示例代码
转换结构体和函数定义
/// <summary>
/// 转换结果结构体
/// </summary>
struct IConvResult {
std::string conv_result_str;// 转换成功的结果 使用新编码的字符串
int error_code = 0; // 错误码
std::string error_msg = {NULL}; // 错误信息
// 判断是否转换成功
bool IsSuccess() const {
return error_code == 0;
}
explicit operator bool() const {
return IsSuccess();
}
bool operator!() const {
return !IsSuccess();
}
bool operator==(int code) const {
return error_code == code;
}
bool operator!=(int code) const {
return error_code != code;
}
const char* c_str() const {
return IsSuccess() ? conv_result_str.c_str() : error_msg.data();
}
};
IConvResult Convert(std::string_view in, const char* fromcode, const char* tocode);
以下是转换函数主体:
UniConv::IConvResult UniConv::Convert(std::string_view in, const char* fromcode, const char* tocode) {
// 转换返回的结果
IConvResult iconv_result;
// 获取 iconv 描述符
auto cd = GetIconvDescriptorS(fromcode, tocode);
if (!cd || (cd.get() == reinterpret_cast<iconv_t>(-1))) {
iconv_result.error_code = errno;
iconv_result.error_msg = GetIconvErrorString(iconv_result.error_code);
return iconv_result;
}
// 输入缓冲区
//std::vector<char> in_buffer(in.begin(), in.end());
const char* inbuf_ptr = in.data(); // 输入缓存
std::size_t inbuf_letf = in.size(); // 输入缓存剩余长度
// 输出缓冲区
constexpr std::size_t initial_buffer_size = 4096;
std::vector<char> out_buffer(initial_buffer_size);
std::string converted_result;
converted_result.reserve(in.size() * 2); // 预分配空间
while (true) {
char* out_ptr = out_buffer.data();
std::size_t out_left = out_buffer.size();
// 执行转换
std::size_t ret = iconv(cd.get(), &inbuf_ptr, &inbuf_letf, &out_ptr, &out_left);
// 写入已转换的数据
converted_result.append(out_buffer.data(), out_buffer.size() - out_left);
if (static_cast<std::size_t>(-1) == ret) {
iconv_result.error_code = errno;
iconv_result.error_msg = GetIconvErrorString(iconv_result.error_code);
break;
}
// 动态扩展缓冲区
if (out_left < 128 && out_buffer.size() < 1048576) { // 最大1MB
out_buffer.resize(out_buffer.size() * 2);
continue;
}
// 检查输入是否处理完毕
if (inbuf_letf == 0) {
// 刷新转换器的内部状态
out_ptr = out_buffer.data();
out_left = out_buffer.size();
ret = iconv(cd.get(), nullptr, &inbuf_letf, &out_ptr, &out_left);
converted_result.append(out_buffer.data(), out_buffer.size() - out_left);
if (static_cast<std::size_t>(-1) == ret) {
iconv_result.error_code = errno;
iconv_result.error_msg = GetIconvErrorString(iconv_result.error_code);
}
break;
}
}
// 返回转换结果
if (iconv_result.error_code == 0) {
converted_result.shrink_to_fit();
iconv_result.conv_result_str = std::move(converted_result);
}
return iconv_result;
}
static std::unordered_map<std::string, IconvSharedPtr> m_iconvDesscriptorCacheMapS;
using IconvSharedPtr = std::shared_ptr <std::remove_pointer<iconv_t>::type>;
///自定义删除器
// 自定义删除器,用于释放 iconv_t 资源
struct IconvDeleter {
void operator()(iconv_t cd) const {
std::cerr << "Closing iconv_t: " << cd << std::endl;
// 只有在 cd 不是无效句柄时才调用 iconv_close
if (cd != reinterpret_cast<iconv_t>(-1)) {
iconv_close(cd);
}
}
};
UniConv::IconvSharedPtr UniConv::GetIconvDescriptorS(const char* fromcode, const char* tocode)
{
std::string key = std::string(fromcode) + ":" + tocode;
std::lock_guard<std::mutex> lock(m_iconvcCacheMutex);
auto it = m_iconvDesscriptorCacheMapS.find(key);
if (it != m_iconvDesscriptorCacheMapS.end()) {
return it->second; // 返回 shared_ptr 的拷贝
}
iconv_t cd = iconv_open(tocode, fromcode);
if (cd == reinterpret_cast<iconv_t>(-1)) {
std::cout << "iconv_open error" << std::endl;
return nullptr;
}
auto iconvPtr = std::shared_ptr<std::remove_pointer_t<iconv_t>>(cd, IconvDeleter());
m_iconvDesscriptorCacheMapS.emplace(key, iconvPtr);
return iconvPtr;
}
以上便是一个简单的封装