`mbstowcs()` Windows vs. Linux



  • @Mathuas sagte in `mbstowcs()` Windows vs. Linux:

    mbstowcs

    MS versteht unter einem Breitzeichen grundsätzlich eine 16 Bit-Darstellung. Also UTF16. Wenn Du UTF32 haben willst, musst Du zunächst aus der vorliegenden UTF8-Bytesequenz den Codepoint berechnen. Und mit dem Codepoint ergeben sich dann die Bytes für UTF32, UTF16 und natürlich für jede andere Zeichenkodierung.

    Die Algorithmen nach c++ umzusetzen dürfte kein Problem sein. MFG



  • Mir ist gerade in den Sinn gekommen, das ich mal bei X11 ein ähnliches Problem hatte, das hatte man es auf folgende Art gelöst.

    https://xopendisplay.hilltopia.ca/2009/Mar/Xlib-tutorial-part-8----a-different-way-to-reach-wide-characters.html

    Dies habe ich jetzt abgeändert, und dies klappt mit Linux und Windows. XChar2b ist fast gleich wie ein UTF8, nur das die Bytes vertauscht sind.

    Es verwundert mich echt, das in keiner C-lib diese Funktion fertig zur Verfügung gestellt wird.
    Haben alle X11 Programmierer welche UTF8 und X11 verwendeten die Funktion selbst geschriebe ?
    Oder auch welche, die FreeTypes verwenden ?



  • @Mathuas sagte in `mbstowcs()` Windows vs. Linux:

    Keiner ein Idee ?

    Mein UTF8-String sieht so aus: "ŸAÄÖÜ" und Länge ermitteln und konvenieren tue ich mit mbstowcs()

    Unter Linux wird folgendes ausgegeben, was in meinen Augen auch richtig ist, weil dort funktioniert FT_Load_Char()

    ŸAÄÖÜ
    len_UTF8: 9
    len_UTF32: 5
    0x00000178 - 0x00000041 - 0x000000C4 - 0x000000D6 - 0x000000DC
    

    Unter Windows mit wine kommt folgendes raus:

    ┼©A├ä├û├£
    len_UTF8: 9
    len_UTF32: 9
    0x00B800C5 - 0x00C30041 - 0x00C30084 - 0x00C30096 - 0x0000009C - 0x00000000 - 0x00000000 - 0x00000000 - 0x00000000
    

    Öhm sicher das unter windows dein input string valides utf8 ist?
    Denn wenn ich dein Beispiel in https://onlinetools.com/utf8/convert-utf8-to-utf16 eingebe kommt folgendes raus:

    0x0178 0x0041 0x00c4 0x00d6 0x00dc
    

    Und das ist auch das ergebnis wie unter linux nur halt in 16bit statt 32bit.
    Was auch kein wunder ist. Denn die test zeichen können in 16Bit abgebildet werden.

    Was für einen compiler hast du genutzt für den windows build?
    Gut möglich dass der compiler den text nicht als utf8 interpretiert hat sondern als eine ANSI codepage. (wenn der test string bestandteil des sourcodes war. Oder die consolen locale nicht auf utf-8 eingestellt war unter windows)
    Gib doch mal die einzelnen bytes deines test strings als integer aus. Dann siehst du ob der test string korrektes utf-8 ist oder nicht.
    Hier die korrekten werte des utf-8 test strings in bytes:

    c5 b8 41 c3 84 c3 96 c3 9c
    

    WEnn der test string bestandteil des sourcecodes ist dann kannst du diesen auch wie folgt schreiben, damit dieser definitiv utf-8 ist

    "\u0178\u0041\u00c4\u00d6\u00dc"
    

    Und irgendwie kann ich nicht glauben dass die verwendung von XChar2b dein Problem unter windows wirklich alleine gefixt hat. Du musst noch mehr an deinem code geändert haben, damit es auch unter windows funktioniert. Oder wie du dein test programm gestartet hast.

    Denn XChar2b ist ein struct mit 2 bytes aka eine 16Bit datenstruktur...
    Und der von dir verlinkte Code macht im grunde nichts anderes als UTF-8 nach UTF-16 zu konvertieren. Unter Windows macht die mbstowcs nichts anderes (wenn die process locale auf eine korrekte utf-8 locale gesetzt ist) bzw. die Verwendung von MultiByteToWideChar da kannst du explizit angeben dass der input string UTF-8 ist.

    Dein Problem ist, dass unter windows vieles anders läuft als unter linux was default text encondings von compiler und runtime betrifft (Für text, welcher in einem byte encoding codiert ist)

    Ein teil der C-API, welches ein "char*" als parameter hat, unter windows kann nichts mit utf-8 anfangen. Das betrifft besonders die C-API für das öffnen von dateien, wie fopen!
    Und ein großteil der Windows API die ein char* als parameter nimmt betrifft das auch.
    Da werden die zeichen als ASCII/ANSI interpretiert.

    http://utf8everywhere.org/#windows



  • Was für einen compiler hast du genutzt für den windows build?
    Gut möglich dass der compiler den text nicht als utf8 interpretiert hat sondern als eine ANSI codepage. (wenn der test string bestandteil des sourcodes war. Oder die consolen locale nicht auf utf-8 eingestellt war unter windows)
    Gib doch mal die einzelnen bytes deines test strings als integer aus. Dann siehst du ob der test string korrektes utf-8 ist oder nicht.
    Hier die korrekten werte des utf-8 test strings in bytes:

    Dies habe ich gemacht

    #include <stdio.h>
    #include <string.h>
    
    const char * TestString =  "ABCÄÖÜŸ";
    
    int main(int argc, char **argv)
    {
        for (int i=0; i<strlen(TestString); i++) {
          printf("%i - ", TestString[i]);
        }
        printf("\n");
    }
    

    Linux:

    $ gcc -o main main.cpp 
    $ ./main 
    65 - 66 - 67 - -61 - -124 - -61 - -106 - -61 - -100 - -59 - -72 -
    

    Windows:

    $ x86_64-w64-mingw32-gcc main.cpp -o main.exe
    $ wine main.exe 
    002c:fixme:winediag:loader_init wine-staging 9.9 is a testing version containing experimental patches.
    002c:fixme:winediag:loader_init Please mention your exact version when filing bug reports on winehq.org.
    65 - 66 - 67 - -61 - -124 - -61 - -106 - -61 - -100 - -59 - -72 -
    

    PS: Ich sehe gerade, das zT. 2 '-' hintereinander kommen.



  • Das ist das Minuszeichen bei den Werten...
    Kannst ja mal %u oder %x verwenden (mit Cast zu unsigned char).



  • Und nutze für den vergleich deinen original test string... und nicht einen anderen...
    Da du dein Programm unter unter linux mit mingw kompilierst und es dann unter windows (via wine) laufen lässt klar wie die ausgabe zustande kommt.

    In diesem Beispiel ist nicht die kodierung des Sourcecodes das Problem, sondern dass unter windows by default die console kein UTF-8 kann sondern ausgegebene byte sequencen auf stdout (z.b. via printf) als ASCII/ANSI zeichen interpretiert.
    Und zusätzlich der process eine ASCII/ANSI locale verwendet wodurch mbstowcs den input string nicht als utf-8 interpretiert!

    Du solltest für den windows build besser MultiByteToWideChar verwenden statt mbstowcs. Dadurch wird definitiv der input als utf-8 interpretiert.

    also z.b. so (Fehlerhandling fehlt hier!):

    // wchars_num enthält auch den null-terminator da -1 für den cbMultiByte parameter übergeben wurde
    // Zu erst die benötigte länge des ziel strings ermitteln
    int wchars_num = MultiByteToWideChar( CP_UTF8 , 0 , TestString , -1, NULL , 0 );
    wchar_t* wstr = new wchar_t[wchars_num];
    // DIe eigendliche konvertierung durchführen
    MultiByteToWideChar( CP_UTF8 , 0 , TestString , -1, wstr , wchars_num );
    // Mach was mit dem string
    delete[] wstr;
    


  • @firefly sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    Du solltest für den windows build besser MultiByteToWideChar verwenden statt mbstowcs. Dadurch wird definitiv der input als utf-8 interpretiert.

    Jetzt habe ich mal ein sauberes Testprogramm gemacht. setlocale(LC_ALL, "") scheint recht wichtig zu sein. Ansonsten gibt es zumindest unter Linux ein SIGESV.

    Das Testprogramm:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <locale.h>
    #if defined(_WIN64)
    #include <windows.h>
    #endif
    
    void WideChar_Test(const char *  str){
        printf("wchar_t size: %li\n",sizeof(wchar_t));
        printf("UTF8:    (%li)  ", strlen(str));
        for (int i=0; i<strlen(str); i++) {
          printf("%hhu - ", str[i]);
        }
        printf("\n");
    
        size_t len = mbstowcs(nullptr, str, strlen(str));
        wchar_t * wc = (wchar_t *) malloc(len * sizeof(wchar_t));
        mbstowcs(wc, str, len);
    
        printf("WideStr: (%li)  ", len);
        for (int i=0; i<len; i++) {
          printf("%hhu - ", wc[i]);
        }
        printf("\n\n");
    
        free(wc);
    }
    
    const char * TestString1 =  "12345678";
    const char * TestString2 =  "ABCÄÖÜŸ";
    
    #if defined(_WIN64)
    void WideChar_Windows(const char *  str){
        printf("wchar_t size: %li\n",sizeof(wchar_t));
        printf("UTF8:    (%li)  ", strlen(str));
        for (int i=0; i<strlen(str); i++) {
          printf("%hhu - ", str[i]);
        }
        printf("\n");
    
        size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
        wchar_t * wc = (wchar_t *) malloc(len * sizeof(wchar_t));
        MultiByteToWideChar( CP_UTF8, 0, str, -1, wc, len);
    
        printf("WideStr: (%li)  ", len);
        for (int i=0; i<len; i++) {
          printf("%hhu - ", wc[i]);
        }
        printf("\n\n");
    
        free(wc);
    }
    #endif
    
    int main(int argc, char **argv)
    {
      printf("Testring 1: %s\n", TestString1);
      printf("Testring 2: %s\n", TestString2);
    //  printf("setlocale: %s", setlocale(LC_ALL, "de_CH.utf8"));
      printf("setlocale: %s\n", setlocale(LC_ALL, ""));
    
      printf("--- Testring 1 ---\n");
      WideChar_Test(TestString1);
      printf("--- Testring 2 ---\n");
      WideChar_Test(TestString2);
    
    #if defined(_WIN64)
      printf("--- MultiByteToWideChar Testring 1 ---\n");
      WideChar_Windows(TestString1);
      printf("--- MultiByteToWideChar Testring 2 ---\n");
      WideChar_Windows(TestString2);
    #endif
    
      return 0;
    }
    

    Das compilieren:

    echo 
    echo ======== Linux ========
    echo 
    rm main
    gcc -o main main.cpp 
    ./main
    echo 
    echo ======== Windows ========
    echo 
    rm main.exe
    x86_64-w64-mingw32-gcc main.cpp -o main.exe
    wine main.exe
    

    Dies Ausgabe:

    $ ./compile.sh 
    
    ======== Linux ========
    
    Testring 1: 12345678
    Testring 2: ABCÄÖÜŸ
    setlocale: de_CH.UTF-8
    --- Testring 1 ---
    wchar_t size: 4
    UTF8:    (8)  49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 - 
    WideStr: (8)  49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 - 
    
    --- Testring 2 ---
    wchar_t size: 4
    UTF8:    (11)  65 - 66 - 67 - 195 - 132 - 195 - 150 - 195 - 156 - 197 - 184 - 
    WideStr: (7)  65 - 66 - 67 - 196 - 214 - 220 - 120 - 
    
    
    ======== Windows ========
    
    002c:fixme:winediag:loader_init wine-staging 9.9 is a testing version containing experimental patches.
    002c:fixme:winediag:loader_init Please mention your exact version when filing bug reports on winehq.org.
    Testring 1: 12345678
    Testring 2: ABCÃ
                    Ãß
    setlocale: German_Switzerland.1252
    --- Testring 1 ---
    wchar_t size: 2
    UTF8:    (8)  49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 -
    WideStr: (8)  49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 -
    
    --- Testring 2 ---
    wchar_t size: 2
    UTF8:    (11)  65 - 66 - 67 - 195 - 132 - 195 - 150 - 195 - 156 - 197 - 184 -
    WideStr: (11)  65 - 66 - 67 - 195 - 30 - 195 - 19 - 195 - 83 - 197 - 184 -
    
    --- MultiByteToWideChar Testring 1 ---
    wchar_t size: 2
    UTF8:    (8)  49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 -
    WideStr: (9)  49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 - 0 -
    
    --- MultiByteToWideChar Testring 2 ---
    wchar_t size: 2
    UTF8:    (11)  65 - 66 - 67 - 195 - 132 - 195 - 150 - 195 - 156 - 197 - 184 -
    WideStr: (8)  65 - 66 - 67 - 196 - 214 - 220 - 120 - 0 -
    

    Was am Anfang schon auffällt, Die Windows Konsole kommt mit UTF8 nicht zurecht.
    MultiByteToWideChar(Windows) und mbstowcs (Linux) spuckt das gleiche aus.



  • @Mathuas sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    Was am Anfang schon auffällt, Die Windows Konsole kommt mit UTF8 nicht zurecht.

    Ja weil windows bei default in der CLI für non "Unicode" (Unter windows ist UNICODE = UTF-16 aka wchar_t) eine ANSI codepage nutzt. Du musst die windows console explizit in den utf-8 modus schalten (afaik durch das setzen einer passenden codepage in der windows console.

    Wobei das AFAIK jetzt nur für die cmd.exe gilt.

    MultiByteToWideChar scheint jetzt auch zu funktionieren. mbstowcs Kommt unter Windows nicht zurecht.

    Öhm wieso mbstowcs nicht funktioniert hab ich dir schon geschrieben...
    Und in deiner ausgabe für den windows build siehst du es auch:

    setlocale: German_Switzerland.1252
    

    das ist keine utf-8 locale... Sondern eine ANSI codepage locale...

    ABer das ganze habe ich dir schon mitgeteilt aber scheinbar ignorierst du diese Informationen komplett...



  • Ich habe die Beispiel von hier probiert, bei Windows spuckt es überall (null).

    https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-170

    Ausser wen ich es ausschreibe wie hier:

    printf("setlocale: %s\n", setlocale(LC_ALL, "German_Switzerland.1252"));
    

    Sowas geht nicht ".UTF8"



  • Versuche es mal beim Aufruf anzugeben, s. Changing the Locale in WINE, anstatt mittels setlocale im Code:
    LANG="de_CH.UTF8" wine main.exe
    bzw.
    LC_ALL="de_CH.UTF8" wine main.exe
    (hier benutzt du die Linux-Nomenklatur, anstatt im Code die Windows-Runtime Nomenklatur)



  • entweder ist das eine eigenart von wine, mingw oder du machst was falsch...

    Folgender code unter windows mit visual studio 2019 liefert non null zurück:

    #include  <stdio.h>
    #include <locale.h>
    
    int main(void)
    {
        printf("setlocale: %s\n", setlocale(LC_ALL, ".UTF8"));
        return 0;
    }
    


  • @Th69 sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    Versuche es mal beim Aufruf anzugeben, s. Changing the Locale in WINE, anstatt mittels setlocale im Code:
    LANG="de_CH.UTF8" wine main.exe
    bzw.
    LC_ALL="de_CH.UTF8" wine main.exe
    (hier benutzt du die Linux-Nomenklatur, anstatt im Code die Windows-Runtime Nomenklatur)

    Nützt ihm aber nicht viel wenn er sein Programm unter einem richtigne windows laufen lassen möchte..



  • Das ist ja auch ersteinmal als Test gedacht, ob es mit Wine so funktioniert.
    Ansonsten müßte er sein Programm unter einem echten Windows 10/11 (z.B. in einer VM) laufen lassen, da ".UTF8" erst dort eingeführt wurde:

    Ab Windows 10, Version 1803 (10.0.17134.0), unterstützt die universelle C-Runtime die Verwendung einer UTF-8-Codepage.

    Und wenn ich die Doku richtig verstehe, so müßte man unter Windows den Bindestrich (statt Unterstrich) verwenden, z.B. "de-CH.UTF8".



  • @firefly sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    entweder ist das eine eigenart von wine, mingw oder du machst was falsch...

    Folgender code unter windows mit visual studio 2019 liefert non null zurück:

    #include  <stdio.h>
    #include <locale.h>
    
    int main(void)
    {
        printf("setlocale: %s\n", setlocale(LC_ALL, ".UTF8"));
        return 0;
    }
    

    Ich habe dies gerade in der VB mit Win10 probiert. Es kommt auch (null)
    Compiliert habe ich es mit wingw32

    x86_64-w64-mingw32-gcc main.cpp -o main.exe
    

    Mit einem (LC_ALL, "") spuckt es in wine und VB-Win10 das gleiche aus: setlocale: German_Switzerland.1252



  • Es ist ein mingw problem.
    https://stackoverflow.com/questions/73185769/build-a-program-with-mingw-on-linux-and-run-under-wine-utf-8-support

    WEnn ich folgenden code unter windows mit visual studio (als mit dem microsoft compiler) im release build baue (unter Windows 10 22H2) und dann das binary unter linux mit wine starte funktioniert es:

    #include  <stdio.h>
    #include <locale.h>
    
    int main(void)
    {
        printf("setlocale: %s\n", setlocale(LC_ALL, ".utf8"));
        printf("%s\n", "\xc5\xb8\x41\xc3\x84\xc3\x96\xc3\x9c");
        return 0;
    }
    

    Output:

    setlocale: English_United Kingdom.65001
    ŸAÄÖÜ
    

    Beachte die .65001 statt .1252 am ende der locale ausgabe.
    65001 ist die codepage nummer für UTF-8



  • @firefly sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    WEnn ich folgenden code unter windows mit visual studio (als mit dem microsoft compiler) im release build baue (unter Windows 10 22H2) und dann das binary unter linux mit wine starte funktioniert es:

    Recht merkwürdig setlocale ist doch in der msvcrt.dll. Somit müssten doch beide Kompilate das gleiche Ergebnis liefern. Oder übersehe ich das etwas ?



  • @Mathuas sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    @firefly sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    WEnn ich folgenden code unter windows mit visual studio (als mit dem microsoft compiler) im release build baue (unter Windows 10 22H2) und dann das binary unter linux mit wine starte funktioniert es:

    Recht merkwürdig setlocale ist doch in der msvcrt.dll. Somit müssten doch beide Kompilate das gleiche Ergebnis liefern. Oder übersehe ich das etwas ?

    Ja denn die "alte" C-Runtime (via msvcrt.dll) von Windows unterstützt kein UTF-8.
    Sondern erst mit der "neuen" C-Runtime (UCRT) wird UTF-8 in der C-Runtime unterstützt.

    Wie man hier sehen kann
    https://gist.github.com/minoki/c3d65d15982f17db0801e8a9b06333bf
    (siehe die ausgaben wo versucht wird eine UTF-8 locale zu setzen.

    Oder auch hier beschrieben:
    https://blog.r-project.org/2022/11/07/issues-while-switching-r-to-utf-8-and-ucrt-on-windows/

    https://stackoverflow.com/questions/67848972/differences-between-msvcrt-ucrt-and-vcruntime-libraries

    Und deine mingw version nutzt die "alte" MS C-Runtime



  • @firefly sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    Ja denn die "alte" C-Runtime (via msvcrt.dll) von Windows unterstützt kein UTF-8.
    Sondern erst mit der "neuen" C-Runtime (UCRT) wird UTF-8 in der C-Runtime unterstützt.

    Wen ich es richtig verstehe, müsste man ucrtbase.dll einbinden ?



  • Kannst es ja mal mit dem DependencyWalker für die kompilierte Datei überprüfen.

    Ansonsten habe ich für LLVM/Clang/LLD (anstatt gcc) unter github.com/mstorsjo/llvm-mingw UCRT-Support gefunden, d.h. Download der neuesten Version unter Releases.



  • @Th69 sagte in &#x60;mbstowcs()&#x60; Windows vs. Linux:

    Kannst es ja mal mit dem DependencyWalker für die kompilierte Datei überprüfen.

    Danke
    Cooles Programm. Ich musste noch die MFC42.dll runterladen.

    Gibt es so etwas auch für Linux ?


Log in to reply