Поиск по этому блогу

суббота, 12 февраля 2011 г.

Использование iconv на C++

Долгое время использовал преобразование кодировок при помощи Glib, функцией g_convert

Например преобразование из UTF-8 в Win-1251
g_convert(s.c_str(), -1, "cp1251", "utf-8", NULL, NULL, &error);

Но по непонятным причинам случайным образом эта функция под FreeBSD отказывалась преобразовывать в одном случае из тысяч из UCS2 в Win-1251 абсолютно нормальный текст, при этом отлично работая под Linux. Возможно причина кроется в том что приложение многопоточное и реализация многопоточности в Фре и Линухе различна. Но факт остается фактом - случайным образом во время преобразоания кодировки из библиотеки Glib на консоль вываливался варнинг, что неполучается использовать закрытый конвертер или чтот еще подобное, сейчас уже не помню и приложение благополчно падало в кор.
Поизучав исходники Glib решил написать свою функцию перекодирования, используя iconv.
Вот только вела она себя както странно.
После продолжительного гугления был найден действительно рабочий вариант http://www.opennet.ru/openforum/vsluhforumID9/4656.html#1 , отличавшийся от моего совсем не многим.
После небольшого тюнинга и проверки работоспособности получился такой вариант:

#include <iconv.h> //как минимум нужно подключить этот хеадер
...
string iconv_recode(string from, string to, string text)
{
iconv_t cnv = iconv_open(to.c_str(), from.c_str());
if (cnv == (iconv_t) - 1)
{
iconv_close(cnv);
return "";
}
char *outbuf;
if ((outbuf = (char *) malloc(text.length()*2 + 1)) == NULL)
{
iconv_close(cnv);
return "";
}
char *ip = (char *) text.c_str(), *op = outbuf;
size_t icount = text.length(), ocount = text.length()*2;

if (iconv(cnv, &ip, &icount, &op, &ocount) != (size_t) - 1)
{
outbuf[text.length()*2 - ocount] = '\0';
text = outbuf;
}
else
{
text = "";
}

free(outbuf);
iconv_close(cnv);
return text;
}

И тут есть момент, упущенный мною в своей неработающей функции. Смотрим строки
...
char *outbuf;
...
char *ip = (char *) text.c_str(), *op = outbuf;
...
if (iconv(cnv, &ip, &icount, &op, &ocount) != (size_t) - 1)

...

А именно в iconv, как параметр, для преобразованной строки передаётся &op, а не &outbuf, как у меня. Попытка в параметрах передать &outbuf вместо &op приводит к неожиданным результатам. Также в дебагере стало видно, что op и outbuf до вызова iconv(cnv, &ip, &icount, &op, &ocount) имеют одинаковый адрес, но после вызова iconv адреса разные. Результат работы iconv находится в outbuf, а op указывает на другой не коректный адрес

2 комментария:

  1. Тут, на самом деле, все в согласии с документациией к iconv() (man 3 iconv). Дело в том, что в своей работе iconv инкрементирует указатели на исходную и результирующие строки и декрементирует размеры буферов. Видимо это было задумано для удобного определения места ошибки при неудачном преобразовании. Хотя, конечно, обескураживает. Я тоже на этом сильно подорвался. Помог разобраться ваш намек на изменение адреса. Спасибо.

    Князев Алексей
    knzsoft@gmail.com

    ОтветитьУдалить
  2. Большое спасибо - меня тоже в тупик поставило, то как надо указатели на выходной буфер задавать.

    ОтветитьУдалить