`mbstowcs()` Windows vs. Linux
-
Bei der Funktion
mbstowcs
spuckt es unter Windows etwas anderes aus als bei Linux.
So wie ich es mitbekommen habe, ist bei Windows einwchar_t
16bit und bei Linux 32bit.Ich bin mit Freetype
FT_Load_Char()
am handieren.
Unter Linux funktioniert es wunderbar, aber bei Window nicht.Gibt es für Windows eine andere Funktion als
mbstowcs
, welche 32bit auspuckt ?Ich vermute es gibt auch eine Funktion, welche aus einem UTF8 ein
char16_t
oderchar32_t
macht.
Nur habe ich mit Google nichts gefunden.
-
Dieser Beitrag wurde gelöscht!
-
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
Wen ich das Ergebniss von Windows anschaue, sieht dies mir recht ähnlich UTF8 aus. Das 0xC3 in der Ausgabe verrät dies.
Was läuft unter Windows schief ?Wen ich es hier überprüfe, kommt das gleiche wie bei Linux raus: https://onlinetools.com/utf8/convert-utf8-to-utf32
Wen ich unter Windows die mit Linux berechnete Zeichenkette direkt anFT_Load_Char()
weitergebe, kommen die Zeichen wie erwartet richtig.
-
Linux/UNIX verwendeten historisch die UCS-4 Kodierung für Unicode Code Points, daher ist wchar_t auch 32Bit groß. Und als „Transportkodierung“ UTF-8. In der Ursprungsversion war der komplette 32Bit Raum für Code Points in Unicode erlaubt.
Windows hingegen setzte auf eine 16Bit Kodierung der Code Points (zuerst UCS-2 und dann UTF-16), so dass zu Problemen kam, wenn man einen UCS-4 String mit vollem 32Bit Umfang nutzt. Daher wurde dann UTF-32 eingeführt, und der Raum für Code Points so eingeschränkt, dass man beim Parsen direkt erkennen kann, ob es sich um einen UTF-32 oder UTF-16 String handelt. Bei UTF-16 und UTF-32 ist unbedingt auf die Endianess zu achten. Bei UNIX/Linux spielt das üblich keine Rolle, da man als Transportkodierung eigentlich immer UTF-8 nutzt, und da die Endianess keine Rolle spielt. Nur Programm intern wird die UTF-32 Kodierung genutzt.
-
@john-0 sagte in `mbstowcs()` Windows vs. Linux:
Windows hingegen setzte auf eine 16Bit Kodierung der Code Points (zuerst UCS-2 und dann UTF-16), so dass zu Problemen kam, wenn man einen UCS-4 String mit vollem 32Bit Umfang nutzt.
Für UCS-2 würde ich dem zustimmen aber nicht für UTF-16. Wenn ein Unicode Zeichen nicht mehr mit einem 16Bit Codepoint dargestellt werden kann, wird dieses Zeichen in UTF-16 mit 2 16bit Codepoints dargestellt.
Denn UTF-16 ist eine variable-length encoding.
UCS-2 ist eine fixed-length encoding.Und Windows nutzt seit Windows 2000 UTF-16 statt UCS-2!
@Mathuas Dass unter windows mbstowcs was anderes liefert könnte daran liegen, dass unter windows die standard locale nicht UTF-8 ist sondern entweder ASCII oder eine ANSI-Codepage.
Und windows interpretiert 8bit strings in den default settings als ASCII oder ANSI. Du musst unter windows entweder explizit via setlocale dem process eine UTF-8 locale setzen oder du verwendest die entsprechende windows API MultiByteToWideChar wo man die encodings von source und dest angeben kann.
-
@firefly sagte in `mbstowcs()` Windows vs. Linux:
@Mathuas Dass unter windows mbstowcs was anderes liefert könnte daran liegen, dass unter windows die standard locale nicht UTF-8 ist sondern entweder ASCII oder eine ANSI-Codepage.
Ich habe folgendes durchprobiert, keine Änderung.
setlocale(LC_ALL, "en_US.utf8"); setlocale(LC_ALL, "de_DE.utf8"); setlocale(LC_ALL, "C");
du verwendest die entsprechende windows API MultiByteToWideChar wo man die encodings von source und dest angeben kann.
Dies habe ich auch ausprobiert, es spuckt das gleiche raus wie
mbstowcs()
.
https://learn.microsoft.com/de-de/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
https://stackoverflow.com/questions/6693010/how-do-i-use-multibytetowidechar
Du schreibst von source und dest, aber man kann nur einen Parameter für Codepage mitgeben.
-
Windows ist hier mal wieder besonders. Die bezeichnungen der locale ist anders als unter linux...
Schau mal in der doku nach wie die bennenung ist.Du schreibst von source und dest, aber man kann nur einen Parameter für Codepage mitgeben.
Stimmt und zwar des source strings. Ergebnis ist ein UTF-16 string. ANderes wirst du unter Windows auch mit mbstowcs nicht bekommen.
Denn diese Methoden konvertieren von einem byte encoding zu wide char encoding mit wchar_t als datentyp.
und unter windows ist dieser nunmal nur 16Bit groß und wird von der windows api für UTF-16 strings genutzt...Da die verschiedenen UTF Kodierungen sich nur unterscheiden welchen basis datentyp verwendet wird lässt sich recht einfach zwischen den verschiedenen Kodierungen konvertieren.
Falls du C++-11 nutzen kannst, dann liefert die c++ standard library passende methoden für so eine konvertierung an.
-
@firefly sagte in `mbstowcs()` Windows vs. Linux:
@john-0 sagte in `mbstowcs()` Windows vs. Linux:
Windows hingegen setzte auf eine 16Bit Kodierung der Code Points (zuerst UCS-2 und dann UTF-16), so dass zu Problemen kam, wenn man einen UCS-4 String mit vollem 32Bit Umfang nutzt.
Für UCS-2 würde ich dem zustimmen aber nicht für UTF-16.
UCS-2 kann trivialerweise nur 16Bit = 65536 Code Points kodieren, das war zuwenig und reichte nur für die Basic Multilingual Plane aus. UTF-16 ist hingegen so definiert, dass Unicode Code Points entweder in 16Bit oder in zwei aufeinanderfolgenden 2×16Bit kodiert werden. Da man mindestens ein Bit im ersten 16Bit Wert benötigt um anzuzeigen, dass ein weiterer 16Bit Wert folgt, sind so maximal 31Bit verfügbar. Es gibt aber weitere Einschränkungen wegen der UTF-16 Kompatibilität sind die Surrogates für Unicode Code Points auch geblockt.
UCS-4 erlaubte Werte von 0x00000000 bis 0xFFFFFFFF für Unicode Code Points, die ließen sich allesamt in UTF-8 kodieren und dekodieren. Davon wurden damals nur ein Bruchteil auch wirklich als gültige Code Points definiert, aber Software sollte damals so geschrieben werden, dass sie mit den kompletten 32Bit umgehen können sollte.
Wegen Windows wurde dann UTF-16 eingeführt, und UCS-4 abgeschafft und durch UTF-32 ersetzt. Zwar sind die definierten gültigen Code Points bei UCS-4 und UTF-32 identisch, aber der mögliche Wertebereich für Code Points wurde für UTF-32 deutlich eingeschränkt, so dass UTF-16 überhaupt erst möglich wurde. Also anstatt 0x00000000 bis 0xFFFFFFFF ist nur noch 0x00000000 bis 0x0010FFFF abzüglich der Surrogates möglich. Das führt nun dazu, dass UTF-8 Code Points in maximal 4 Bytes kodiert und nicht mehr in maximal 6 Bytes wie für UCS-4.
-
@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.
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.
-
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 zuunsigned 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 `mbstowcs()` 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 einSIGESV.
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) undmbstowcs
(Linux) spuckt das gleiche aus.
-
@Mathuas sagte in `mbstowcs()` 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 `mbstowcs()` 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"
.