StringLoader



  • LoadString kopiert, so wie du es verwendest, den String in einen Puffer den du übergibst.
    Wenn du immer den selben Puffer übergibst, wie in deinem Beispielcode, dann wird dieser natürlich immer überschrieben.

    Der nächste Bug ist, dass deine Funktion nur einen TCHAR zurückgibt, also nur ein Zeichen, und dieses eine Zeichen noch dazu von einer ungültigen Adresse liest. Nämlich eins ausserhalb ("hinter") des Arrays temp .

    Was funktionieren sollte (ungetestet!):

    TCHAR const* MyLoadString(UINT id)
    {
        TCHAR buffer[sizeof(TCHAR const*) / sizeof(TCHAR)] = {};
        int const rc = LoadString(GetModuleHandle(NULL), id, buffer, 0);
        if (rc == 0)
            return _T("");
        else
        {
            // LoadString hat, weil wir nBufferMax == 0 übergeben haben, die Adresse (!)
            // des Strings in den Puffer kopiert (siehe MSDN Doku).
            TCHAR const* p;
            memcpy(&p, buffer, sizeof(TCHAR const*));
            assert(p);
            return p;
        }
    }
    


  • hustbaer schrieb:

    Was funktionieren sollte (ungetestet!):

    Sicher nicht! In der String-Table findest UNICODE Strings. Du musst also LoadStringW verwenden, und tatsächlich machst Du mit 0 im letzten Parameter und LoadStringA den Stack kaputt. Zusätzlich sind die Strings nicht Null-Terminiert. Die Länge steht im Rückgabewert[-1]. Und für jemanden, der einen einzelnen Character für einen ganzen String hält, ist das erheblich zu kompliziert!



  • Interessant.
    Dann bleibt wohl wirklich nur String Klassen zu verwenden.

    typedef std::basic_string<TCHAR> tstring;
    
    tstring MyLoadString(UINT id)
    {
    	tstring buffer(1024, 0);
    	int const rc = LoadString(GetModuleHandle(NULL), id, &buffer[0], buffer.size());
    	if (rc <= 0)
    		return tstring();
    	else
    	{
    		buffer.resize(rc);
    		return buffer;
    	}
    }
    

    ps:

    und tatsächlich machst Du mit 0 im letzten Parameter und LoadStringA den Stack kaputt.

    Weisst du wieso das so ist? Aus der Doku kann ich das nicht rauslesen...



  • hustbaer schrieb:

    Weisst du wieso das so ist? Aus der Doku kann ich das nicht rauslesen...

    LoadStringA ist ziemlich "stumpf" implementiert. Nach der Konvertierung per WCSToMBEx wird der Return-Buffer korrekt terminiert. Die Buffer-Größe steht jetzt aber auf 0. Daher wird nun Parameter3[--Parameter4] auf 0 gesetzt und -1 zurückgegeben. Dokumentiert ist das tatsächlich nicht, passiert aber dennoch (zumindest bis einschließlich 7, >= 8 habe ich nicht getestet).

    Ansonsten, wenn man so eine Funktion unbedingt benötigt, ginge vielleicht auch das hier:

    std::wstring MyLoadString(UINT uID)
    {
        wchar_t wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
    
        if(0 >= LoadStringW(GetModuleHandle(nullptr), uID, wsz, 0))
        {
            PCWSTR pwsz = *reinterpret_cast<PCWSTR*>(wsz);
            return std::wstring(pwsz, pwsz[-1]);
        }
    
        return L"";
    }
    

    Und wenn Du nun nicht für Unicode compilerst, warum auch immer, bekommst Du wenigstens einen Fehler gemeldet. 🙂



  • Ich habe es jetzt mal ausprobiert, das hier reicht hin:

    std::wstring MyLoadString(UINT uID)
    {
        WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
        int len = LoadStringW(GetModuleHandle(nullptr), uID, wsz, 0);
        return std::wstring(*reinterpret_cast<PCWSTR*>(wsz), len);
    }
    


  • Hey, ich danke euch beiden vielmals für eure Hilfe!
    @ Hustbaer
    Ich habe dein erstes Beispiel ausprobiert, nur ist da das Problem, dass er alle Strings die im gleichen Table sind läd. Hab ka warum das so ist und konnte es auch nicht beheben.
    @ Mox
    Dein letztes Beispiel habe ich als nächstes versucht. Das Funktioniert sehr gut. Beim aufrufen muss ich das allerdings so machen:

    MyLoadString(IDS_MSG_CONTENT_BEENDEN).c_str()
    

    Denke das soll auch so sein? Zudem ist es so, das ich das:

    wc.lpszClassName = MyLoadString(IDC_WINDOW_MAIN_CLASS_NAME).c_str();
    

    nicht machen kann. Da wirft er mir zur Laufzeit eine Fehlermeldung um die Ohren:
    **
    Ausnahme (erste Chance) bei 0x77A9E11B (ntdll.dll) in test.exe: 0xC0000005: Zugriffsverletzung beim Lesen an Position 0x00F9C000
    **
    Und im VS Zeigt er mir:
    0x0000004d <Fehler beim Lesen der Zeichen der Zeichenfolge>
    <Speicher kann nicht gelesen werden>
    Hat jemand eine Idee, warum das so ist?
    LG



  • Mox schrieb:

    Ich habe es jetzt mal ausprobiert, das hier reicht hin:

    std::wstring MyLoadString(UINT uID)
    {
        WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
        int len = LoadStringW(GetModuleHandle(nullptr), uID, wsz, 0);
        return std::wstring(*reinterpret_cast<PCWSTR*>(wsz), len);
    }
    

    Wenn ich das richtig verstehe, soll mit diesem Aufruf von LoadStringW ein Zeiger in den Puffer wsz geschrieben werden. Der aber bietet doch gar nicht genug Platz für einen Zeiger?!



  • tomy86 schrieb:

    Hat jemand eine Idee, warum das so ist?
    LG

    Ja, ich hätte da eine Idee. Nur so viel: Merke Dir einfach das gelieferte Objekt:

    std::wstring cls = MyLoadString(IDC_WINDOW_MAIN_CLASS_NAME);
    wc.lpszClassName = cls.c_str();
    


  • Belli schrieb:

    Der aber bietet doch gar nicht genug Platz für einen Zeiger?!

    Sondern? Wie viel fehlt denn?



  • WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
    

    Da sizeof(WCHAR) mindestens (oder immer?) zwei ist, sizeof(WCHAR*) aber die Größe eines Zeigers, dürfte mindestens (oder immer?) die Hälfte fehlen.
    Auf meinem 32bit - System ist der oben reservierte Puffer nur zwei Byte groß ...



  • Belli schrieb:

    Auf meinem 32bit - System ist der oben reservierte Puffer nur zwei Byte groß ...

    Du reservierst Speicher für 2 WCHAR, nicht BYTE! Und da sizeof(WCHAR) == 2, wie Du richtig festgestellt hast, hast Du Speicher für 2*2 Bytes reserviert.



  • Jo, und da passt kein Zeiger rein, wie ich hier:

    Belli schrieb:

    Wenn ich das richtig verstehe, soll mit diesem Aufruf von LoadStringW ein Zeiger in den Puffer wsz geschrieben werden. Der aber bietet doch gar nicht genug Platz für einen Zeiger?!

    angemerkt habe.

    Edit:
    Ne, Quatsch!
    Reserviert werden nur 2 Byte - und da passt kein Zeiger rein.

    Es wird ja:

    WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
    

    Größe eines Zeigers (auf WCHAR, was aber unerheblich ist, da ein Zeiger immer dieselbe Größe haben sollte) dividiert durch Größe eines WCHAR (= 2) reserviert, also Platz für einen halben Zeiger.



  • @Belli
    Sagen wir WCHAR ist 2 Byte gross.
    Sagen wir ein Zeiger ist 4 Byte gross.
    sizeof(WCHAR*) / sizeof(WCHAR) ist dann 2.
    Es werden also zwei WCHAR s (!!) reserviert, also 2 * sizeof(WCHAR) == 4 Byte, also genug Platz für einen Zeiger.
    Ist das wirklich so schwer zu verstehen?

    Anders wäre es wenn da

    char bufferForAPointer[sizeof(WCHAR*) / sizeof(WCHAR)]; // ACHTUNG, FALSCH!!!
    

    stehen würde.
    Tut's aber nicht.
    Weil die Funktion ja einen WCHAR -Puffer erwartet.



  • Sorry, ich hatte ein Brett vorm Kopf! Ich hab immer nur die 2 in den [] gesehen ... Klar, der Datentyp ist ja WCHAR, und somit selbst schon 2 Byte groß ...

    Asche auf mein Haupt!

    Aber warum macht man das so ... mhm ... umständlich, und nicht einfach

    WCHAR *wsz;
    

    ?
    Ah ja, dann stimmt der Datentyp für die Funktion nicht mehr ... mhm, manchmal lohnt es sich doch, genauer hinzusehen ...



  • @Mox
    Bitte nicht reinterpret_cast für sowas verwenden.
    Dass ich hier memcpy verwendet habe hat durchaus einen (guten) Grund.
    Nämlich dass memcpy hier standardkonform ist, reinterpret_cast dagegen nicht.

    Und zwar weil reinterpret_cast hier einerseits strict-aliasing verletzt (mit MSVC AFAIK derzeit egal, aber auf sowas sollte man sich nicht verlassen), und andrerseits weil man damit ein Alignment-Problem bekommen könnte.



  • Oh ja, an Alignment habe ich in der Tat keinen Gedanken verschwendet, da hast hast Du natürlich absolut recht! Aber memcpy sieht immer so teuer aus. 😉

    Dann eben so:

    std::wstring MyLoadString(UINT uID)
    {
        union
        {
            WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
            WCHAR* pwsz;
        };
    
        int len = LoadStringW(GetModuleHandle(nullptr), uID, wsz, 0);
        return std::wstring(pwsz, len);
    }
    


  • Immer noch nicht standardkonform fürchte ich.
    Nimm memcpy .
    Der Compiler optimiert das vermutlich eh komplett weg.



  • hustbaer schrieb:

    Immer noch nicht standardkonform fürchte ich.

    Weil? Alignment kann es ja nun nicht mehr sein.



  • Naja, ist vielleicht ne Grauzone.

    Vorweg: ich mein(t)e mit "nicht standardkonform" natürlich dass das Programm laut Standard "undefined behavior" (UB) hat.

    Du übergibst nen WCHAR* auf wsz und LoadStringW schreibt da was rein.
    Dann liest du aus pwsz was raus.
    Würdest du direkt was nach wsz schreiben wäre es deswegen UB, weil du ne Union nur als das lesen darfst als was sie zuletzt beschrieben wurde.

    Wäre LoadStringW in deinem Programm implementiert, und würde direkt den übergebenen Zeiger dereferenzieren um reinzuschreiben (also ohne memcpy oder "selbst mittels char -Zeiger kopieren"), dann wäre es zusätzlich nochmal UB wegen einer strict aliasing Verletzung*.

    Jetzt ist LoadStringW natürlich extern implementiert, und man könnte argumentieren dass der Compiler daher ja nicht wissen kann wie LoadStringW den Wert schreibt, und es daher OK sein muss. Allerdings übergibst du an LoadStringW nen Zeiger auf wsz , und nicht einen auf pwsz . Daher könnte man genau so argumentieren dass LoadStringW auch nur wsz beschreiben kann.
    Leider reichen meine Kenntnisse des Standard nicht aus um mich hier weiter festzulegen. Ich vermute dass klar aus dem Standard hervorgeht ob es OK ist oder nicht. Aber selbst wenn es OK sein sollte, würde ich den Code lieber mit memcpy schreiben. Einfach weil es mit memcpy sicher OK ist, und ich keinen guten Grund sehe es ohne memcpy zu schreiben.

    * Die genaue strict-aliasing Regel bitte selbst nachzulesen. Hab nicht alle Details exakt im Kopf, und diese Regel ist mir auch etwas zu heikel als dass ich versuchen würde sie mal eben schnell vereinfacht zusammenzufassen.



  • hustbaer schrieb:

    Würdest du direkt was nach wsz schreiben wäre es deswegen UB, weil du ne Union nur als das lesen darfst als was sie zuletzt beschrieben wurde.

    Naja, dann eben doch den Cast, das ist doch ganz egal. Die Union stellt doch trotzdem sicher, dass die Ausrichtung passt.

    Ansonsten wird mir das hier deutlich zu esoterisch. Wenn das UB ist, dann bitte sehr. Das nehme ich gerne und bewusst in Kauf (noch). Ich arbeite hier viel zu viel mit ganz kleinen Systemen, als das ich mir das nutzlose Kopieren von Daten leisten kann und will. Und wegen irgendwelcher Phantome, die sich zu zudem nicht einmal richtig beim Namen nennen lassen und sich vielleicht am 03.03.33 auswirken, werde ich in dieser Richtung sicherlich nichts ändern.

    Das ist natürlich nur meine Meinung, kann selbstverständlich auch völliger Scheiß sein. Aber solange ich nicht weiß was genau das Problem ist und nicht eine einzige Implementierung kenne, die darüber tatsächlich stolpert...


Anmelden zum Antworten