StringLoader



  • Hi, ich möchte eine Funktion schreiben die aus einer Ressourcen Datei die Strings ausliest. Die Funktion dazu sieht so aus:

    union STRLOAD{
    	TCHAR temp[MAX_STR_LEN];
    } strload;
    
    TCHAR Load_String(UINT ID_STRING){
    	LoadString(GetModuleHandle(NULL), ID_STRING, strload.temp, MAX_STR_LEN);
    	return strload.temp[MAX_STR_LEN];
    }
    

    Ich muss dazu sagen, ich habe schon tausend verschiedene Dinge probiert. Das ist jetzt die letzte Variante.
    Das Problem ist jetzt, das immer nur ein String geladen wird. Was mache ich da falsch? Warum wird die Variable temp nicht jedes mal überschrieben und ausgegeben?
    Ach und wenn ich in vs13 beim return rein breakpoint setze, kann ich sehr wohl sehen, das jefals der richtige String geladen wird.
    Das ist ein Aufrufbeispiel:

    MessageBox(NULL, (LPCWSTR)Load_String(IDS_MSG_CONTENT_ERROR), (LPCWSTR)Load_String(IDS_MSG_TITEL_ERROR), MB_OK);
    

    Danke schon mal.
    lg



  • Mit Verlaub, das ist ja wohl völliger Quark! So liefert Dir z.B. GetModuleHandle(NULL) immer die HINSTANCE der EXE. Wenn Du das in einer DLL machst, bekommst Du gleich mal das falsche geliefert. Warum nimmst Du nicht einfach das Handle, dass Du in WinMain oder DllMain als ersten Parameter bekommst?

    Dann liefert Deine Funktion keinen String zurück, sondern lediglich einen einzelnen Character. Der Character, den Du zurück lieferst, lieget dann sogar auch noch außerhalb Deines Arrays.

    Und zum Schluss versuchst Du mit einem Cast, den einzelnen Character in einen String umzuwandeln. Und nun wunderst Du Dich, dass MessageBox nicht wirklich was verwertbares macht.

    Ach Du liebe Zeit...

    Übrigens: Eine Funktion, die String aus den Ressourcen lädt, brauchst Du nicht zu schreiben. Eine solche Funktion gibt es bereits: LoadString.



  • Wie gesagt, ist nur eines von vielen Dingen die ich schon versucht habe. Habe es auch schon mit der normalen hinstance gemacht. War lediglich ein anderer Versuch.
    Ich möchte aber nicht zick Tausend globale Variablen haben, deshalb eine Funktion.
    Ich bekomme aber bei der einigen Ausgabe die er macht, nicht nur ein Zeichen zurück, sondern alle. Und auch dem vs Zeigt er mit alles an und nicht nur ein Char.



  • 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.


Anmelden zum Antworten