c++使用iconv进行字符编码格式转换

发布于:2025-03-28 ⋅ 阅读:(32) ⋅ 点赞:(0)

iconv

在这里插入图片描述
这是一个典型的C风格的函数

std::size_t iconv (iconv_t cd、const char* * inbuf、size_t * inbytesleft、
char* * outbuf、size_t * outbytesleft);

iconv官方文档

使用方法

  1. 打开一个转换句柄
iconv_t iconv_open = iconv_open(const char* tocode, const char* fromcode);

iconv_t 的底层实际类型是void*

iconv_open 可能带来的错误:

  • EINVAL 不支持的编码转换
  • ENOMEM “Out of memory” 内存不足
  1. 执行转换

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

  • 另一种情况是当inbufNULL*inbufNULL,但outbuf不为NULL*outbuf不为NULL时。在这种情况下,iconv()函数会尝试将cd的转换状态设置为初始状态,并在*outbuf处存储相应的移位序列。从*outbuf开始,最多会写入*outbytesleft字节。如果输出缓冲区没有足够的空间来存储此重置序列,它会将errno设置为E2BIG并返回(size_t) -1。否则,它会根据写入的字节数增加*outbuf并减少*outbytesleft

    第三种情况是当inbufNULL*inbufNULL,且outbufNULL*outbufNULL时。在这种情况下,iconv()函数会将cd的转换状态设置为初始状态。

  • iconv()函数返回在此次调用期间以不可逆方式转换的字符数量;可逆转换不会被计数。如果发生错误,iconv()会返回(size_t) -1,并设置errno以指示具体的错误

注意

在每次调用iconv()的系列操作中,最后一次调用应将inbuf*inbuf设置为NULL,以便刷新任何部分转换的输入。

尽管inbufoutbuf的类型为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;
}


以上便是一个简单的封装