communityWir suchen ständig neue Tutorials und Artikel! Habt ihr selbst schonmal einen Artikel verfasst und seid bereit dieses Wissen mit der Community zu teilen? Oder würdet ihr gerne einmal über ein Thema schreiben das euch besonders auf dem Herzen liegt? Dann habt ihr nun die Gelegenheit eure Arbeit zu veröffentlichen und den Ruhm dafür zu ernten. Schreibt uns einfach eine Nachricht mit dem Betreff „Community Articles“ und helft mit das Angebot an guten Artikeln zu vergrößern. Als Autor werdet ihr für den internen Bereich freigeschaltet und könnt dort eurer literarischen Ader freien Lauf lassen.

CString Management - CString in char * Drucken E-Mail
Benutzerbewertung: / 5
SchwachPerfekt 
Geschrieben von: StarShaper   
Dienstag, den 20. September 2005 um 18:30 Uhr
Beitragsseiten
CString Management
CString in char *
VARIANT, STRINGTABLE und temporäre Objekte
std::string und Effizienzbetrachtungen
Alle Seiten

CString in char * II: Unter Benutzung von GetBuffer

Es gibt für CStrings eine spezielle Methode um diese zu modifizieren. Es handelt sich um die Operation GetBuffer. Diese gibt ihnen den Pointer zum Buffer, welcher als beschreibbar angesehen wird, zurück. Wenn sie nur die Character ändern wollen oder den string verkürzen möchten, können sie das nun tun.

CString s(_T("File.ext"));
LPTSTR p = s.GetBuffer();
LPTSTR dot = strchr(p, '.'); // OK, sollte s.Find verwenden...
if(p != NULL)
    *p = _T('\0');
s.ReleaseBuffer();

Das ist der erste und einfachste Weg GetBuffer zu benutzen. Sie liefern kein Argument, so dass der Default Wert 0 verwendet wird, was soviel bedeutet wie "gib mir einen Pointer zum string, ich verspreche dir den string nicht zu erweitern". Sobald sie ReleaseBuffer aufrufen wird die aktuelle Länge des strings berechnet und im CString gespeichert. Innerhalb des Scopes (Sichtbarkeitsbereichs) einer GetBuffer/ReleaseBuffer Sequenz betone ich hier ausdrücklich folgendes: Sie dürfen niemals irgend eine Methode von CString an einem CString anwenden wessen Buffer sie haben! Der Grund ist, das die Integrität eines CString Objektes nicht garantiert ist solange ReleaseBuffer nicht aufgerufen wurde. Schauen sie sich den Code unten etwas genauer an:

CString s(...);
LPTSTR p = s.GetBuffer();
//...zahlreiche Dinge passieren mit dem Pointer p
int n = s.GetLength(); // Schlecht!!! Gibt wahrscheinlich einen falschen Wert zurück
s.TrimRight();         // Schlecht!!! Keine Garantie das es funktioniert
s.ReleaseBuffer();     // OK
int m = s.GetLength(); // Garantiert richtig
s.TrimRight();         // Auch korrekt

Stellen sie sich vor, sie möchten den string vergrößern. In diesem Fall müssen sie wissen wie groß der string werden wird. Das ist so wie bei der Deklaration eines char Array's von dem sie wissen das die Bufferlänge niemals die reservierte Anzahl überschreiten wird.

char buffer[1024];

Dazu äquivalent in der CString Welt ist:

LPTSTR p = s.GetBuffer(1024);

Dieser Aufruf liefert nicht nur den Pointer zum Buffer, sondern garantiert auch das die Bufferlänge (mindestens) 1024 byte groß ist.

Beachten sie auch, das der Wert einen strings selbst im Read-Only Speicher liegt, wenn sie den Pointer zu diesem konstanten string besitzen. Ein Versuch in diesem zu speichern, unabhänig davon ob sie GetBuffer ausgeführt haben, wird in einem Zugriffsfehler enden. Ich habe das für CStrings nicht verifiziert, aber ich habe C Programmierer gesehen die diesen Fehler öfters begehen.

Ein üblicher, schlechter Ratschlag, der noch von C Programmierern stammt ist zuerst einen Buffer fester Größe zu allokieren, sprintf zu benutzen und diesen anschließend einem CString zuzuordnen.

char buffer[256];
sprintf(buffer, "%......", args, ...); // ...die Daten
CString s = buffer;

Die bessere Lösung ist:

CString s;
s.Format(_T("%....", args, ...);

Beachten sie, dass das immer funktioniert. Wenn ihr string länger als 256 byte ausfällt zerschlagen sie außerdem nicht gleich den Stack!

Ein anderer oft begangener Fehler ist den offensichtlich klevereren Weg zu gehen und statt einer fixen Buffergröße diesen dynamisch zu allokieren. Das ist aber sogar noch dämlicher.

int len = lstrlen(parm1) + 13 + lstrlen(parm2) + 10 + 100;
char *buffer = new char[len];
sprintf(buffer, "%s is equal to %s, valid data", parm1, parm2);
CString s = buffer;
....
delete [] buffer;

Schließlich kann das Ganze sehr leicht mit zwei kurzen Zeilen realisiert werden:

CString s;
s.Format(_T("%s is equal to %s, valid data"), parm1, parm2);

Beachten sie außerdem das die sprintf Beispiele nicht Unicode kompatibel sind (obwohl sie natürlich tsprintf benutzen und _T( ) um den zu formatierenden string schreiben können), aber die Grundidee ist das sie sich mit dem obigen Code sehr viel leichter tun. Denn der andere ist zum einen sehr viel länger und deshalb auch sehr viel anfälliger für Fehler.

CString in char * III: Schnittstelle zu einer Control

Eine sehr gängige Methode ist ein CString Wert an ein Control zu übergeben, z.B. ein CTreeCtrl. Während MFC eine Reihe an angenhemen Überladungen für diesen Vorgang zur Verfügung stellt, wird gewöhlich in den meisten Situationen die "raw" Form des Updates benutzt. Und daher müssen sie den auf einen string zeigenden Pointer im TVITEM speichern, welcher im TVINSERTITEMSTRUCT beinhaltet ist.

TVINSERTITEMSTRUCT tvi;
CString s;
// ...irgend etwas s zuordnen
tvi.item.pszText = s; // Compiler beschwert sich
// ...andere Dinge
HTREEITEM ti = c_MyTree.InsertItem(&tvi);

Nun, warum beschwert sich der Compiler? Es sieht nach einer perfekten Zuweisung aus! Aber wenn sie tatsächlich auf die Struktur schauen, werden sie sehen dass das Memberobjekt in der TVITEM Struktur deklariert ist, so wie unten gezeigt.

LPTSTR pszText;
int cchTextMax;

Daher ist die Zuweisung keinem LPCTSTR zugeordnet und der Compiler weiß nicht wie er den rechten Teil der Zuweisung in einen LPTSTR casten soll.

OK, sie denken sie können das beheben indem sie einen expliziten Cast durchführen.

tvi.item.pszText = (LPCTSTR)s; // Compiler beschwert sich aber immer noch!

Nein, es funktioniert immer noch nicht! Der Grund liegt darin, dass sie versuchen einen LPCTSTR einem LPTSTR zuzuweisen. Eine Operation die in C/C++ verboten ist. Sie können diese Methode nicht versehentlich verwenden um einen konstanten Pointer in einen nicht konstanten umzuwandeln. Das würde den Sinn von Konstanten ad absurdum führen. Wenn es ihnen gelingt, verwirren sie eventuell den Optimizer, welcher ihren Anweisungen blind vertraut wenn es um die Entscheidung geht das Programm zu optimieren. Zum Beispiel, wenn sie das hier machen:

const int i = ...;
//...führe eine Reihe von Operationen durch
     ... = a[i];  // erste Benutzung
// ...weitere Dinge
     ... = a[i];  // zweite Benutzung 

Der Compiler kann in diesem Beispiel darauf vertrauen das der Wert von i sowohl bei der ersten als auch bei der zweiten Verwendung gleich ist, weil die Variable i von ihnen mit const initialisiert wurde. Darüberhinaus kann der Compiler sogar die Adresse von a[] bei der ersten Benutzung vorherberechnen und diesen Wert für nachfolgende Verwendungen, wie z.B. bei der zweiten Benutzung wiederverwenden. Dies ist natürlich besser als das jedesmal neu zu berechnen. Wenn sie in der Lage wären folgenden Code zu schreiben (in der Form würde der Compiler natürlich einen Fehler wegen inkompatibler Typumwandlung melden aber das soll auch nur als Beispiel dienen),

const int i = ...;
int *p = &i;
//...führe eine Reihe von Operationen durch
     ... = a[i];  // erste Benutzung
// ...mache irgendetwas
     (*p)++;      // den Compiler in die Irre führen
// ...weiterer Code
     ... = a[i];  // zweite Benutzung

würde der Compiler auf die Konstanz von i vertrauen und konsequent auf die Konstanz des Ziels von i. Aber wir haben den Compiler in die Irre geführt indem wir zunächst einen Pointer vom Typ int auf i haben zeigen lassen und anschließend den Inhalt von p, und somit den von i, inkrementiert haben. In so einem Fall würde das Programm im Debug Modus (ohne Optimierungen) ein anderes Verhalten an den Tag legen als im Release Modus mit voller Optimierung. Das ist nicht gut. Deshalb wird der Versuch den Pointer auf i, also auf eine veränderbare Referenz zeigen zu lassen vom Compiler als falsch diagnostiziert. Das ist der Grund warum der weiter oben angesprochene Fall mit dem expliziten (LPCTSTR) Cast nicht helfen würde.

Warum nicht einfach den Member als einen LPCTSTR deklarieren? Weil die Struktur sowohl für das Lesen als auch das Schreiben zu dem Control verwendet wird. Wenn sie zum Control schreiben wird der Text Pointer tatsächlich wie ein LPCTSTR behandelt, aber wenn sie vom Control lesen benötigen sie einen beschreibbaren string. Die Struktur kann nicht zwischen ihrem Gebrauch für Inputs und ihrem Gebrauch für Outputs unterscheiden.

Deshalb werden sie oft etwas in meinem Code finden was so aussieht wie das hier:

tvi.item.pszText = (LPTSTR)(LPCTSTR)s;

Dies castet den CString in einen LPCTSTR, welches mir auf diese Weise die Adresse des strings liefert, welchen ich dann in einen LPTSTR umwandeln kann. Anschließend bin ich in der Lage diesen zuzuweisen. Beachten sie dass das nur gültig ist wenn sie den Wert als Datum im Stile für Set- oder Insert-Methoden verwenden. Sie können das nicht machen wenn sie versuchen Daten zu holen!

Sie benötigen eine etwas veränderte Methode wenn sie versuchen Daten, so wie den Wert der in einem Control gespeichert ist, zu bekommen. Zum Beispiel für einen CTreeCtrl mit Benutzung der GetItem Methode. Hier möchte ich nun den Text eines Elementes erhalten. Ich weiß das der Text nicht größer ist als MY_LIMIT. Deshalb kann ich folgendes schreiben:

TVITEM tvi;
// ...passende Initialisierung von andern Feldern von tvi
tvi.pszText = s.GetBuffer(MY_LIMIT);
tvi.cchTextMax = MY_LIMIT;
c_MyTree.GetItem(&tvi);
s.ReleaseBuffer();

Der oben gepostete Code arbeitet auch für jede Art von Set-Methoden, ist aber nicht unbedingt erforderlich, weil sie bei einer Set-/Insert-Methode keinen string schreiben. Wenn sie einen CString schreiben, müssen sie sich vergewissern das der Buffer beschreibbar ist. Das ist was GetBuffer macht. Noch einmal, wenn sie erst einmal den Aufruf von GetBuffer getätigt haben, dürfen sie nichts anderes am CString machen solange sie nicht ReleaseBuffer aufrufen.

CString in BSTR

Wenn sie mit ActiveX programmieren, werden sie manchmal einen Wert benötigen welcher als BSTR Typ dargestellt wird. Ein BSTR ist ein abgezähler string, ein wide-character (Unicode) string auf Intel Plattformen und kann eingebettete Nullbyte Character (auch NUL Character genannt) enthalten.

Sie können einen CString in einen BSTR konvertieren indem sie die CString Methode AllocSysString aufrufen.

CString s;
s = ... ; // irgendwas
BSTR b = s.AllocSysString()

Der Pointer b zeigt auf ein neu allokiertes BSTR Objekt welches eine Kopie des CString inklusive des abschließenden Nullbytes ist. Das kann nun an irgend eine Schnittstelle übergeben werden welche beim Aufruf nach einem BSTR verlangt. Normalerweise wird ein BSTR von der Komponente welches es empfängt gelöst. Wenn sie dies manuell machen müssen, müssen sie den Aufruf

::SysFreeString(b);

verwenden um den string wieder freizugeben.

Hintergrund ist, das die Entscheidung wie man strings darstellen sollte die zu ActiveX Steuerelementen gesendet werden, in einem Microsoft internen Kleinkrieg endete. Die Visual Basic Leute gewannen und der string Typ BSTR (akronym für "Basic String") war das Ergebnis.

BSTR in CString

Infolgedessen das ein BSTR ein abgezählter Unicode string ist, können sie die Standard-Umwandlungen benutzen um einen 8-Bit CString daraus zu machen. Tatsächlich ist das eine "In-Box" Lösung, da es spezielle Konstruktoren gibt welche ANSI strings in Unicode konvertieren und vice versa. Sie können auch BSTRs als Ergebnisse in einem VARIANT Typ erhalten, welcher der Rückgabetyp bei verschiedenen COM und automatisierten Aufrufen ist.

Zum Beispiel wenn sie in einer ANSI Anwendung

BSTR b;
b = ...; // irgendwas
CString s(b == NULL ? L"" : s)

schreiben funktioniert das gut für einen einzelnen BSTR string, weil ein spezieller Konstruktor existiert welcher einen LPCWSTR (was eigentlich ein BSTR ist) aufnimmt und in einen ANSI string konvertiert. Der spezielle Test wird benötigt, weil ein BSTR NULL sein kann und die Konstruktoren nicht besonders gut mit NULL Inputs arbeiten (Danke an Brian Ross für den Hinweis!). Das funktioniert also nur für einen BSTR welcher aus einem einzelnen string mit abschließendem Nullbyte besteht. Sie müssen etwas mehr tun um strings welche mehrfache Nullbytes enthalten zu konvertieren. Beachten sie das eingebettete Nullbytes generell nicht besonders gut in CStrings funktionieren und deshalb vermieden werden sollten.

Erinnern sie sich an die Regel in C/C++ bezüglich LPWSTR Typen? Wenn sie einen LPWSTR haben passt dieser zum Parameter Typ von LPCWSTR (das funktionert aber nicht umgekehrt!).

Im Unicode Modus ist das nur der Konstruktor:

CString::CString(LPCTSTR);

Wie bereits oben angedeutet, gibt es im ANSI Modus (Multi-Byte) einen speziellen Konstruktor dafür.

CString::CString(LPCWSTR);

Dies ruft eine interne Funktion auf um den Unicode string in einen ANSI string zu konvertieren. (Im Unicode Modus gibt es einen speziellen Konstruktor der einen LPCSTR aufnimmt, einen 8-Bit ANSI string und diesen zu einem Unicode string erweitert). Wie gesagt, beachten sie die Einschränkungen die durch den Test für einen BSTR Wert welcher NULL ist entstehen.

Es gibt zudem ein zusätzliches Problem welches auch oben bereits angesprochen wurde. BSTRs können eingebettete Nullbytes enthalten. CStrings können aber nur mit einem Nullbyte pro string umgehen. Das bedeutet das CStrings die falsche Länge für einen string berechnen werden welcher mehrere eingebettete Nullbytes enthält. Sie müssen das Problem selbst lösen. Wenn sie auf die Konstruktoren in strcore.cpp schauen, werden sie sehen das dort Alle lstrlen oder vergleichbare Algorithmen benutzen um die Länge zu berechnen.

Beachten sie außerdem das die Konvertierung von Unicode in ANSI die ::WideCharToMultiByte Umwandlung mit bestimmten Argumenten benutzt welche sie vielleicht nicht gerne haben. Wenn sie eine andere Umwandlung möchten als im Standard, müssen sie diese selbst schreiben.

Wenn sie den Code als Unicode kompilieren, benötigen sie nur eine einfache Zuweisung:

CString convert(BSTR b)
{
    if(b == NULL)
        return CString(_T(""));
    CString s(b); // im UNICODE Modus
    return s;
}

Wenn sie sich aber im ANSI Modus befinden, benötigen sie eine etwas komplexere Lösung um den string zu konvertieren. Der nachfolgende Code ist eine solche Lösung. Beachten sie das dieser Code die gleichen Argumentenwerte in ::WideCharToMultiByte benutzt, wie die impliziten Konstruktoren für CString benutzen, so dass sie diese Technik nur dann verwenden würden wenn sie diese Parameter ändern möchten um die Konvertierung auf irgend eine andere Art und Weise zu vollziehen. Zum Beispiel in dem sie einen anderen Default Character, anderer Flag's, etc. definieren.

CString convert(BSTR b)
{
    CString s;
    if(b == NULL)
       return s; // Leer für NULL BSTR
#ifdef UNICODE
    s = b;
#else
    LPSTR p = s.GetBuffer(SysStringLen(b) + 1); 
    ::WideCharToMultiByte(CP_ACP,            // ANSI Code Page
                          0,                 // keine Flags
                          b,                 // Source widechar string
                          -1,                // Annahme NUL-terminiert
                          p,                 // Ziel Buffer
                          SysStringLen(b)+1, // Länge des Ziel Buffers
                          NULL,              // Benutze default char des Systems
                          NULL);             // egal wenn default benutzt wird
    s.ReleaseBuffer();
#endif
    return s;
}

Achten sie darauf das ich keine Rücksicht darauf nehme was passiert wenn der BSTR Unicode Character beinhaltet die nicht dem 8-Bit Zeichensatz entsprechen. Deshalb habe ich NULL für die beiden letzten Parameter angegeben. Eventuell möchten sie dies zum Beispiel ändern.



Zuletzt aktualisiert am Mittwoch, den 22. August 2007 um 17:22 Uhr
 
AUSWAHLMENÜ