Verbesserung der Umwandlung eines ANSI- in einen Wide-String gesucht



  • Da wär ich mal wieder. 😃

    Ich habe die Makros jetzt mal in eine Funktion gepackt. (Immerhin sollen sie später in mein Programm kommen und da will ich auch mit Funktionen und nicht mit ätzenden Makros arbeiten. Und so kann man schonmal gucken, ob es überhaupt geht.) Das Problem ist jetzt aber: Solange sich der String in der Konvertierungsfunktion befindet, funktioniert er. Aber außerhalb ist er dann wieder ungültig. Und das, obwohl ich die LPCSTR-Variable per Referenz übergeben habe:

    #include <windows.h>
    #include <atlbase.h>
    
    void Convert(LPCSTR &a, // Übergabe des umzuwandelnden
                            // Strings per Referenz!
                 LPCWSTR w)
    {
    	USES_CONVERSION;
    
    	a = W2A(w);
    	// Hier ist noch alles in Ordnung:
    	// Adresse von a: 0x0065fcbc
    	// Wert    von a: "Ich bin ein String"
    
    	MessageBoxA(NULL, a, "Convert", MB_OK);
    	// Korrekte Ausgabe
    }
    
    int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
    {
    	LPCSTR str = NULL;
    
    	Convert(str, L"Ich bin ein String.");
    	// Korrekte Adresse, aber der String ist leer:
    	// Adresse von str: 0x0065fcbc
    	// Wert    von str: ""
    
    	MessageBoxA(NULL, str, "Main", MB_OK);
    	//Leere Message-Box
    }
    

    Sollte der mit alloca generierte Speicherplatz nicht solange gültig sein wie auch die Variable gültig ist? Warum also ist zwar die Adresse von str aus WinMain noch immer die gleiche wie die von a aus Convert, während aber alle Zeichen auf 0 gesetzt sind?


  • Mod

    <kopfschüttel>
    Das ist doch kein Auto-Pointer System!
    Die konvertierten Strings werden auf dem Stack abgelegt! Deine Funktion kann so nicht funktionieren. Sie funktionieren so lange wie der aktuelle Stack-Frame nicht verlassen wird.

    Das was Du hier willst bedeutet den konsequenten Einsatz von std::string oder CString und bei Bedarf eben die Konvertierungen an dieser Stelle. Die konvertierten Strings dürfen nur in dem Funktionsblock verwendet werden in dem sie definiert wurden. RÜckgabeist so unmöglich, genauso wie das persistente Speichern in anderen Strukturen.



  • Martin Richter schrieb:

    <kopfschüttel>
    Das ist doch kein Auto-Pointer System!
    Die konvertierten Strings werden auf dem Stack abgelegt! Deine Funktion kann so nicht funktionieren. Sie funktionieren so lange wie der aktuelle Stack-Frame nicht verlassen wird.

    Aber ich habe die Variable doch per Referenz übergeben. Das heißt, der Stack-Rahmen dafür endet erst am Ende der Main-Funktion.

    Martin Richter schrieb:

    Das was Du hier willst bedeutet den konsequenten Einsatz von std::string oder CString und bei Bedarf eben die Konvertierungen an dieser Stelle. Die konvertierten Strings dürfen nur in dem Funktionsblock verwendet werden in dem sie definiert wurden. RÜckgabeist so unmöglich, genauso wie das persistente Speichern in anderen Strukturen.

    Deshalb hab ich den String ja auch nicht zurückgegeben, sondern per Referenz übergeben.


  • Mod

    NES-Spieler schrieb:

    Martin Richter schrieb:

    <kopfschüttel>
    Das ist doch kein Auto-Pointer System!
    Die konvertierten Strings werden auf dem Stack abgelegt! Deine Funktion kann so nicht funktionieren. Sie funktionieren so lange wie der aktuelle Stack-Frame nicht verlassen wird.

    Aber ich habe die Variable doch per Referenz übergeben. Das heißt, der Stack-Rahmen dafür endet erst am Ende der Main-Funktion.

    Aber der Strin Array selbst liegt doch im Stackrahmen der Funktion!
    Es gibt keine eferenz-Zählung für einfache Pointer! Punkt!

    Genauso falsch ist das hier und kommt Deinem Code sehr nahe:

    char *MyString()
    {
    char szTest[20];
    strcpy(szTest,"TEST");
    return szTest;
    }
    

    Martin Richter schrieb:

    Das was Du hier willst bedeutet den konsequenten Einsatz von std::string oder CString und bei Bedarf eben die Konvertierungen an dieser Stelle. Die konvertierten Strings dürfen nur in dem Funktionsblock verwendet werden in dem sie definiert wurden. RÜckgabeist so unmöglich, genauso wie das persistente Speichern in anderen Strukturen.

    Deshalb hab ich den String ja auch nicht zurückgegeben, sondern per Referenz übergeben.

    Kennst Du den Unterschied zwischen einer Referenz und einem Zeiger und vor allem einer Referenz auf einen Zeiger?
    Wenn nicht beschäftige dich mal damit und versuche vor allem mal raus zu bekommen welchen Einfluss eine Referenz auf den Storage hat auf den sie verweist... 🕶

    Antwort (hier vorab): Keinen!



  • Martin Richter schrieb:

    NES-Spieler schrieb:

    Aber ich habe die Variable doch per Referenz übergeben. Das heißt, der Stack-Rahmen dafür endet erst am Ende der Main-Funktion.

    Aber der Strin Array selbst liegt doch im Stackrahmen der Funktion!
    Es gibt keine eferenz-Zählung für einfache Pointer! Punkt!

    Ich weiß, daß es keine Referenzzählung gibt. (Hätte ich das geglaubt, dann hätte ich die Funktion ja einfach so geschrieben, daß der umgewandelte String per return zurückgegeben wird und nicht, daß ich den umzuwandelnden String per Referenz übergebe.) Ich hatte gedacht, daß der Speicherplatz auf dem Stack dann der ursprünglich deklarierten Variable, die per referenz übergeben wurde, zugewiesen wird, aber ich sehe jetzt, was Du meinst.

    Gibt es nun überhaupt eine Möglichkeit, einen String in einen Unicode-String umzuwandeln, ohne
    a) ohne den String dynamisch auf dem Heap zu erstellen (was ja eine manuelle Löschung mit delete erfordern würde)
    b) ohne die Benutzung von Makros
    c) ohne den Konvertierungscode an der entsprechenden Stelle immer wieder neu zu schreiben
    d) ohne die Verwendung von C++-Strings (denn wenn ich einen String aus einer WNDCLASSEXA-Struktur in einen String aus einer WNDCLASSEXW-Struktur umwandeln will, nützt mir das nichts)?



  • Ich glaube NES-Spieler kennt den Unterschied zwischen einem Zeiger und einem String bzw. Array nicht.



  • Ironischerweise gibt es da in C nichtmal wirklich einen durch die Quellcodesyntax gesicherten Unterschied:

    int main()
    {
    	char c = 'A';
    
    	char *p = &c; // p ist ein char-Pointer.
    	p = "Hallo";  // Nein, Moment! p ist ein String.
    	p[3] = 'X';   // Oder doch ein char-Array?
    }
    

    😉


  • Mod

    Mein angebotenes Verfahren funktioniert solange Du eben nicht so einen Quatsch machst und Zeiger und Referenzen bedienst.



  • Martin Richter schrieb:

    Mein angebotenes Verfahren funktioniert solange Du eben nicht so einen Quatsch machst und Zeiger und Referenzen bedienst.

    O.k., gut. Dann sag mir doch bitte noch, wie die Konvertierungsfunktion letztendlich genau aussehen muß. Wenn ich es so mache, wie Du vorgeschlagen hast:

    #include <iostream>
    #include <windows.h>
    #include <atlbase.h>
    
    using namespace std;
    
    string Funktion()
    {
        string str = "Hallo";
    
        return str;
    }
    
    int main()
    {
        USES_CONVERSION;
        LPCWSTR winAPIString = A2W(Funktion().c_str());
    
        cout << winAPIString << '\n';
    }
    

    dann muß ich ja entweder Makros bemühen oder das, was sich hinter den Makros befindet, direkt hinschreiben. Zweitere Möglichkeit bedeutet allerdings, daß ich all die Sachen, die sich hinter den Makros befinden, jedesmal, bei jedem einzelnen String, manuell neu deklarieren muß (zum Beispiel die Variablen aus USES_CONVERSION).
    Wie würde das ganze also schlußendlich aussehen, wenn ich eine einzelne, für sich allein stehende Funktion haben will, die mir einen String in einen anderen konvertiert?
    Ich habe nun schon diverse Tips hier gelesen, aber ich habe noch nicht eine in sich geschlossene Funktion gesehen, die man von überall aufrufen kann. Wie müßte diese aussehen? Wie sieht eine ConvertString-Funktion aus, die intern vielleicht noch die Makros USES_CONVERSION und A2W/W2A benutzt (die Makros auflösen kann ich ja dann später selbst), mit der es aber nicht mehr nötig ist, diese Makros außerhalb (in der main oder einer anderen Funktion) noch irgendwo zu verwenden? Wie sieht eine ConvertString-Funktion aus, bei der ich in der main nur noch schreiben muß:

    LPCSTR str = NULL;
    
    /* ... */ ConvertString(/* ... */ L"Tadaaa! Es klappt!" /* ... */);
    
    MessageBoxA(str);
    // Ausgabe:
    // Tadaaa! Es klappt!
    

    Wenn Du mir sagen könntest, wie das geht, dann wäre ich Dir wirklich dankbar.



  • Wie würde das ganze also schlußendlich aussehen, wenn ich eine einzelne, für sich allein stehende Funktion haben will, die mir einen String in einen anderen konvertiert?

    Die Antwort wurde bereits mehrfach gegeben, nur du akzeptierst sie nicht.

    Es gibt in C++ keine Strings, also kannst du auch keine zurückgeben. Du kannst nur entweder einen Zeiger auf ein char Array zurückgeben, oder ein Objekt. Wenn du einen Zeiger auf ein char Array zurückgibst hast du ein Problem mit der Speicherverwaltung, und Objekte willst du nicht.
    Ergo: was du wünscht ist *unmöglich*.



  • O.k., ich hab es jetzt so geregelt, daß mir die Funktion einen String auf dem Heap erzeugt und zurückgibt. Dazu noch eine Delete-Funktion. Das ganze sieht so aus:

    LPSTR CreateANSIString(LPCWSTR str)
    {
    	if (str)
    	{
    		const UINT LENGTH = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);
    		LPSTR newString = new CHAR[LENGTH];
    
    		WideCharToMultiByte(CP_ACP, 0, str, -1, newString, LENGTH, NULL, NULL);
    
    		return newString;
    	}
    	else
    		return NULL;
    }
    
    void DeleteANSIString(LPSTR str)
    {
    	if (str)
    		delete[] str;
    }
    

    Nur ist jetzt das Problem: Die Strings, die in Windows immer verwendet werden (z.B. WNDCLASSEXA::lpszClassName) sind vom Typ LPCSTR und die ließen sich nicht mit delete[] löschen. Was kann man da tun?


  • Mod

    Dann deklariere die zweite Funktion doch mit einem const Zeiger?

    PS: Ich Frage mich warum wir diese ganze Diskussion geführt haben.
    Nachträglich diese Funktion einzubauen ist mit Sicherheit genauso schwer, wie eine vernünftige Klasse zu verwenden!



  • Martin Richter schrieb:

    Dann deklariere die zweite Funktion doch mit einem const Zeiger?

    Hab ich. Dann hat er sich geweigert, das delete durchzuführen.

    Martin Richter schrieb:

    PS: Ich Frage mich warum wir diese ganze Diskussion geführt haben.
    Nachträglich diese Funktion einzubauen ist mit Sicherheit genauso schwer, wie eine vernünftige Klasse zu verwenden!

    Aber ich kann keine Klasse verwenden, weil die Strings, die ich benutze, nicht von mir selbst deklariert wurden. Es ging mir darum, sowas hier zu realisieren:

    WNDCLASSEXW wndClass;
    // ...
    wndClass.lpszClassName = L"Irgendein String";
    // ...
    
    if (/* OS unterstützt Unicode nicht*/)
    {
        WNDCLASSEXA wndClassA;
        // ...
        wndClassA.lpszClassName = Convert(wndClass.lpszClassName);
        // ...
        RegisterClassExA(&wndClassA);
    }
    else
        RegisterClassExW(&wndClass);
    

    In diesem Fall nützt es mir überhaupt nichts, irgendeine Wrapperklasse zu benutzen, da der umzuwandelnde String von Windows vorgegeben ist. Und ich kann ja nicht die WNDCLASSEX-Struktur umdefinieren.



  • CStringW strClassNameW = L"Irgendein String";
    CStringA strClassNameA = strClassNameW;
    
    WNDCLASSEXW wndClass;
    // ...
    wndClass.lpszClassName = strClassNameW;
    // ...
    
    if (/* OS unterstützt Unicode nicht*/)
    {
        WNDCLASSEXA wndClassA;
        // ...
        wndClassA.lpszClassName = strClassNameA;
        // ...
        RegisterClassExA(&wndClassA);
    }
    else
        RegisterClassExW(&wndClass);
    
        if (UnicodeIsSupported()) 
            return CreateWindowExW(/* ... */ strClassNameW /* ... */); 
        else 
            return CreateWindowExA(/* ... */ strClassNameA /* ... */);
    


  • Und was ist an der Idee, für jeden String noch einen weiteren lokalen String zu haben, jetzt besser?


  • Mod

    Deterministische Zerstörung! Keine Leaks!



  • Einfache Konvertierung und automatische Speicherfreigabe bei Scope-Ende.

    Alternativen:
    - Auf Unterstützung für Windows 9x/ME verzichten und nur noch Unicode benutzen
    - MSLU verwenden



  • sri schrieb:

    Alternativen:
    - Auf Unterstützung für Windows 9x/ME verzichten und nur noch Unicode benutzen

    Hm, klar. Vor allem, wenn man in 99% der Fälle überhaupt kein Unicode braucht. Ich finde es immer toll, wenn da steht, daß das Programm nicht funktioniert, nur weil darauf bestanden wird, daß der 08/15-String "Hauptfenster" als L"Hauptfenster" geschrieben wird.

    sri schrieb:

    - MSLU verwenden

    Ich halte es für inakzeptabel, bei einer 50 KB-Anwendung Abhängigkeiten zu erstellen, die nur aufgrund der Faulheit des Programmierers existieren.


  • Mod

    1. Mit korrekter T-Notation ist Unicode gar kein Problem.
    2. Arbeitet Windows sowieso nur in Unicode. Jeder Deiner MBCS Strings wird also extra für Dich umgewandelt.
    3. CreateWindow benötigt kein L"", CreateWindowW benötigt L"" Notation, CreateWindow "". Korrekte Schreibwweise ist also _T("") for CreateWindow
    4. Wenn Dich Unicode also nicht interessiert, warum wilst Du es dann bitte für die Windows API verwenden. Schalte es ab...
    Dann können wir uns in Zukunft solche überflüssigen Diskussionen sparen.



  • Ich weiß, daß ich den ganzen Ärger hätte vermeiden können, wenn ich einfach nur mit LPCTSTR gearbeitet hätte. Aber es geht mir um folgendes: Die A und die W-Versionen der Funktionen in der WinAPI sind ja vor dem Benutzer nicht versteckt. Das heißt, er kann wählen, ob er die A-, die W- oder die T-Version aufruft. Normalerweise benutzt man T. In dem Fall ist die Frage nach Unicode abhängig davon, ob man das UNICODE-Makro gesetzt hat. Aber es ist eben auch möglich, völlig aus diesem System auszubrechen und Funktionsaufrufe zu benutzen, die ANSI oder Unicode benutzen, ohne auf das Makro zu achten. Da man in der WinAPI also nicht gezwungen ist, seine Ausgabe vom UNICODE-Makro abhängig zu machen und auch solche Dinge möglich sind:

    void DreiMessageBoxen()
    {
        MessageBoxA(NULL, "ANSI", "ANSI", MB_OK);
        MessageBoxW(NULL, L"Unicode", L"Unicode", MB_OK);
        MessageBox(NULL, TEXT("Makroabhängig"), TEXT("???"), MB_OK);
    }
    

    wollte ich meinen Code (welcher ein paar allgemeingültige Wrapperklassen für Fenstererstellungen beinhaltet) eben nicht einfach nur mit T-Strings schreiben, sondern es dem Programmierer ermöglichen, selbst zu wählen, ob er abhängig vom UNICODE-Makro arbeitet oder den Datentyp hardcodiert. Deshalb biete ich Funktionen für LPCSTR und LPCWSTR an.


Anmelden zum Antworten