Tenemos una aplicación C ++ implementada en RHEL usando ICU.

Tenemos una situación en la que necesitamos convertir UChar * a wchar_t * en linux. Usamos u_strToWCS para realizar la conversión.

#include <iostream>
#include <wchar.h>

#include "unicode/ustring.h"

void convertUnicodeStringtoWideChar(const UChar* cuniszSource,
                                    const int32_t cunii32SourceLength,
                                    wchar_t*& rpwcharDestination,
                                    int32_t& destCapacity)
{
  UErrorCode uniUErrorCode = U_ZERO_ERROR;

  int32_t pDestLength = 0;

  rpwcharDestination     = 0;
  destCapacity = 0;

  u_strToWCS(rpwcharDestination,
             destCapacity,
             &pDestLength,
             cuniszSource,
             cunii32SourceLength,
             &uniUErrorCode);

  uniUErrorCode = U_ZERO_ERROR;
  rpwcharDestination = new wchar_t[pDestLength+1];
  if(rpwcharDestination)
  {
    destCapacity = pDestLength+1;

    u_strToWCS(rpwcharDestination,
               destCapacity,
               &pDestLength,
               cuniszSource,
               cunii32SourceLength,
               &uniUErrorCode);

    destCapacity = wcslen(rpwcharDestination);
  }
} //function ends

int main()
{
    //                     a       ä       Š       €    (     王     )
    UChar input[20] = { 0x0061, 0x00e4, 0x0160, 0x20ac, 0xd87e, 0xdd29, 0x0000 };
    wchar_t * output;
    int32_t outlen = 0;
    convertUnicodeStringtoWideChar( input, 6, output, outlen );
    for ( int i = 0; i < outlen; ++i )
    {
        std::cout << std::hex << output[i] << "\n";
    }
    return 0;
}

Esto funciona bien para los caracteres ingresados hasta 65535 (ya que UChar se implementa como uint16_t internamente en Linux). No puede convertir caracteres fuera del plano multilingüe básico (por ejemplo, CJK Unified Ideographs Extension B)

¿Alguna idea sobre cómo realizar la conversión?

Actualización 1 : OK. Estaba mirando direcciones equivocadas. u_strToWCS funciona bien. El problema surge porque necesito pasar esa cadena ancha a una aplicación Java en Windows usando CORBA. Como wchar_t en Linux es de 32 bits, necesito encontrar una manera de convertir 32 bits wchar_t a 16 bits wchar_t

Actualización 2 : El código que he usado se puede encontrar aquí

2
D3XT3R 20 mar. 2017 a las 12:02

2 respuestas

La mejor respuesta

El siguiente es el código para convertir caracteres anchos codificados UTF-32 a UTF-16

//Function to convert a Unicode string from platform-specific "wide characters" (wchar_t) to UTF-16.
void ConvertUTF32ToUTF16(wchar_t* source,
                         const uint32_t sourceLength,
                         wchar_t*& destination,
                         uint32_t& destinationLength)
{

  wchar_t wcharCharacter;
  uint32_t uniui32Counter = 0;

  wchar_t* pwszDestinationStart = destination;
  wchar_t* sourceStart = source;

  if(0 != destination)
  {
    while(uniui32Counter < sourceLength)
    {
      wcharCharacter = *source++;
      if(wcharCharacter <= 0x0000FFFF)
      {
        /* UTF-16 surrogate values are illegal in UTF-32
           0xFFFF or 0xFFFE are both reserved values */
        if(wcharCharacter >= 0xD800 && 
           wcharCharacter <= 0xDFFF)
        {
          *destination++ = 0x0000FFFD;
          destinationLength += 1;
        }
        else
        {
          /* source is a BMP Character */
          destinationLength += 1;
          *destination++ = wcharCharacter;
        }
      }
      else if(wcharCharacter > 0x0010FFFF)
      {
        /* U+10FFFF is the largest code point of Unicode Character Set */
        *destination++ = 0x0000FFFD;
        destinationLength += 1;
      }
      else
      {
        /* source is a character in range 0xFFFF - 0x10FFFF */
        wcharCharacter -= 0x0010000UL;
        *destination++ = (wchar_t)((wcharCharacter >> 10) + 0xD800);
        *destination++ = (wchar_t)((wcharCharacter & 0x3FFUL) + 0xDC00);
        destinationLength += 2;
      }

      ++uniui32Counter;
    }

    destination = pwszDestinationStart;
    destination[destinationLength] = '\0';
  }

  source = sourceStart;
} //function ends
0
D3XT3R 3 abr. 2017 a las 05:21

En C ++ 11 y versiones posteriores, esta conversión está en la biblioteca estándar, en el encabezado <codecvt>. Aquí hay un código de muestra que convierte entre UTF-16, UCS-4 y wchar_t. (Se rompe en libstdc ++ 6.4.9 debido a un error que se ha corregido en el árbol de desarrollo).

#include <codecvt>
#include <cstdlib>
#include <cstring>
#include <cwctype>
#include <iostream>
#include <locale>
#include <vector>

using std::cout;
using std::endl;
using std::exit;
using std::memcmp;
using std::size_t;

using std::wcout;

int main(void)
{
  constexpr char16_t msg_utf16[] = u"¡Hola, mundo! \U0001F600"; // Shouldn't assume endianness.
  constexpr wchar_t msg_w[] = L"¡Hola, mundo! \U0001F600";
  constexpr char32_t msg_utf32[] = U"¡Hola, mundo! \U0001F600";
  constexpr char msg_utf8[] = u8"¡Hola, mundo! \U0001F600";

  // May vary from OS to OS>  "" is the most standard, but might require, e.g. "en_US.utf8".
  constexpr char locale_name[] = "";
  std::locale::global(std::locale(locale_name)); //
  wcout.imbue(std::locale());

  const std::codecvt_utf16<wchar_t, 0x1FFFF, std::little_endian> converter_w;
  const size_t max_len = sizeof(msg_utf16);
  std::vector<char> out(max_len);
  std::mbstate_t state;
  const wchar_t* from_w = nullptr;
  char* to_next = nullptr;

  converter_w.out( state, msg_w, msg_w+sizeof(msg_w)/sizeof(wchar_t), from_w, out.data(), out.data() + out.size(), to_next );

  
  if (memcmp( msg_utf8, out.data(), sizeof(msg_utf8) ) == 0 ) {
    wcout << L"std::codecvt_utf16<wchar_t> converts to UTF-8, not UTF-16!" << endl;
  } else if ( memcmp( msg_utf16, out.data(), max_len ) != 0 ) {
    wcout << L"std::codecvt_utf16<wchar_t> conversion not equal!" << endl;
  } else {
    wcout << L"std::codecvt_utf16<wchar_t> conversion is correct." << endl;
  }
  out.clear();
  out.resize(max_len);

  const std::codecvt_utf16<char32_t, 0x1FFFF, std::little_endian> converter_u32;
  const char32_t* from_u32 = nullptr;
  converter_u32.out( state, msg_utf32, msg_utf32+sizeof(msg_utf32)/sizeof(char32_t), from_u32, out.data(), out.data() + out.size(), to_next );

  if ( memcmp( msg_utf16, out.data(), max_len ) != 0 ) {
    wcout << L"std::codecvt_utf16<char32_t> conversion not equal!" << endl;
  } else {
    wcout << L"std::codecvt_utf16<char32_t> conversion is correct." << endl;
  }

  wcout << msg_w << endl;
  return EXIT_SUCCESS;
}

Esas dos facetas quedarán en desuso en C ++ 17, pero no todas las facetas en <codecvt> lo son. En particular, la biblioteca estándar admitirá std::codecvt<char, char, std::mbstate_t>, std::codecvt<char16_t, char, std::mbstate_t>, std::codecvt<char32_t, char, std::mbstate_t> y std::codecvt<wchar_t, char, std::mbstate_t>.

No entra en la fuente de estos datos UTF-16 en Linux, pero eso podría sugerir un enfoque. Si es para trabajar con archivos, puede usar imbue() en una secuencia con una faceta para convertir los datos a medida que se leen y escriben, y si es para trabajar con el marco Qt, tanto QString como { {X2}} proporciona funciones de conversión. Aún así, la UCI debería admitir todo el rango de UTF-16.

Actualización 1

La pregunta realmente era cómo convertir en la dirección opuesta, de cadenas anchas a UTF-16. Mi ejemplo lo hace, pero si desea utilizar la UCI, tiene u_strFromWCS(), u_strFromUTF32() y UnicodeString::fromUTF32().

Si su razón para preferir la ICU a la STL es que las facetas del convertidor de STL afirman ser independientes de la localidad, observe que todas esas funciones de convertidor de ICU afirman que también son independientes de la localidad. ¡Esto se debe a que la conversión entre diferentes codificaciones UTF es completamente algorítmica e independiente de la configuración regional! (Otras cosas, como el orden de clasificación y las asignaciones de mayúsculas y minúsculas no lo son, pero eso es). De hecho, STL le permite solicitar una faceta de convertidor desde una ubicación específica con locale::use_facet<codecvt<...>>() si lo desea, y esto no está en desuso en C ++ 17. Sin embargo, solo las conversiones hacia y desde UTF-8 deben implementarse de esta manera. "Además, cada objeto de entorno local construido en un programa C ++ implementa su propio (específico del entorno) versiones de estas cuatro especializaciones ". En mis pruebas, las implementaciones existentes de la biblioteca no son compatibles con locale().use_facet<std_codecvt<wchar_t,char16_t,mbstate_t>>().

Actualización 2

Estoy volviendo a publicar un convertidor manual wchar_t a utf_16 de mi respuesta aquí. Toma un std::wstring y devuelve un std::u16string, pero el algoritmo podría adaptarse fácilmente a cualquier otro contenedor. Sin embargo, un u16string será al menos tan eficiente como cualquier otra estructura de datos que requiera memoria dinámica.

Un cambio que puede querer hacer es que asigno suficiente memoria para el peor de los casos, dada la longitud de la cadena de entrada, luego shrink_to_fit() después. Esto no debería desperdiciar más memoria que la codificación de su cadena como lo hizo UTF-32 en primer lugar. Sin embargo, es extremadamente improbable que ninguno de sus datos esté en el BMP, por lo que podría hacer un pase inicial para contar la cantidad de memoria que necesitará la conversión, o asumir que habrá muy pocos pares sustitutos en el uso y aceptación en el mundo real La improbable posibilidad de tener que cambiar el tamaño y copiar la matriz de destino.

#include <cassert>
#include <cwctype>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <locale>
#include <string>

#if _WIN32 || _WIN64
// Windows needs a little non-standard magic for this to work.
#include <io.h>
#include <fcntl.h>
#include <locale.h>
#endif

using std::size_t;

void init_locale(void)
// Does magic so that wcout can work.
{
#if _WIN32 || _WIN64
  // Windows needs a little non-standard magic.
  constexpr char cp_utf16le[] = ".1200";
  setlocale( LC_ALL, cp_utf16le );
  _setmode( _fileno(stdout), _O_U16TEXT );
#else
  // The correct locale name may vary by OS, e.g., "en_US.utf8".
  constexpr char locale_name[] = "";
  std::locale::global(std::locale(locale_name));
  std::wcout.imbue(std::locale());
#endif
}

std::u16string make_u16string( const std::wstring& ws )
/* Creates a UTF-16 string from a wide-character string.  Any wide characters
 * outside the allowed range of UTF-16 are mapped to the sentinel value U+FFFD,
 * per the Unicode documentation. (http://www.unicode.org/faq/private_use.html
 * retrieved 12 March 2017.) Unpaired surrogates in ws are also converted to
 * sentinel values.  Noncharacters, however, are left intact.  As a fallback,
 * if wide characters are the same size as char16_t, this does a more trivial
 * construction using that implicit conversion.
 */
{
  /* We assume that, if this test passes, a wide-character string is already
   * UTF-16, or at least converts to it implicitly without needing surrogate
   * pairs.
   */
  if ( sizeof(wchar_t) == sizeof(char16_t) ) {
    return std::u16string( ws.begin(), ws.end() );
  } else {
    /* The conversion from UTF-32 to UTF-16 might possibly require surrogates.
     * A surrogate pair suffices to represent all wide characters, because all
     * characters outside the range will be mapped to the sentinel value
     * U+FFFD.  Add one character for the terminating NUL.
     */
    const size_t max_len = 2 * ws.length() + 1;
    // Our temporary UTF-16 string.
    std::u16string result;

    result.reserve(max_len);

    for ( const wchar_t& wc : ws ) {
      const std::wint_t chr = wc;

      if ( chr < 0 || chr > 0x10FFFF || (chr >= 0xD800 && chr <= 0xDFFF) ) {
        // Invalid code point.  Replace with sentinel, per Unicode standard:
        constexpr char16_t sentinel = u'\uFFFD';
        result.push_back(sentinel);
      } else if ( chr < 0x10000UL ) { // In the BMP.
        result.push_back(static_cast<char16_t>(wc));
      } else {
        const char16_t leading = static_cast<char16_t>( 
          ((chr-0x10000UL) / 0x400U) + 0xD800U );
        const char16_t trailing = static_cast<char16_t>( 
          ((chr-0x10000UL) % 0x400U) + 0xDC00U );

        result.append({leading, trailing});
      } // end if
    } // end for

   /* The returned string is shrunken to fit, which might not be the Right
    * Thing if there is more to be added to the string.
    */
    result.shrink_to_fit();

    // We depend here on the compiler to optimize the move constructor.
    return result;
  } // end if
  // Not reached.
}

int main(void)
{
  static const std::wstring wtest(L"☪☮∈✡℩☯✝ \U0001F644");
  static const std::u16string u16test(u"☪☮∈✡℩☯✝ \U0001F644");
  const std::u16string converted = make_u16string(wtest);

  init_locale();

  std::wcout << L"sizeof(wchar_t) == " << sizeof(wchar_t) << L".\n";

  for( size_t i = 0; i <= u16test.length(); ++i ) {
    if ( u16test[i] != converted[i] ) {
      std::wcout << std::hex << std::showbase
                 << std::right << std::setfill(L'0')
                 << std::setw(4) << (unsigned)converted[i] << L" ≠ "
                 << std::setw(4) << (unsigned)u16test[i] << L" at "
                 << i << L'.' << std::endl;
      return EXIT_FAILURE;
    } // end if
  } // end for

  std::wcout << wtest << std::endl;

  return EXIT_SUCCESS;
}
2
Community 20 jun. 2020 a las 09:12