Hilfe, C++Builder 2009 macht mich fertig
-
Hallo,
ich habe gerade den C++ Builder 2009 installiert und wollte nun versuchen mal ein Projekt zu portieren. (Habe ja auch eine Sicherungskopie)
Der neue Datenty UnicodString macht mich kaputt.
Dort, wo ich eine Messagebox verwende möchte er nun kein const char * (zeiger mehr übergeben bekommmen (Beipiel variablexy.c_char*) sondern ein const wchar_t* (also ein Zeiger auf eine WideCharactereintrag). Da ich in dem Projekt häufiger Meldungen ausgeben lasse, bin ich gezwungen das Programm anzupassen.
Meine Frage an euch ist, warum diese Änderung nun im Builder 2009 integriert wurden.
Was muss ich insgesamt tun. Dynamische Texte als UnicodeString deklarieren um sie dann als wchar_t* zu übergeben. Womit muss ich noch rechnen?
-
Im 2009'er ist der frühere Datentyp 'String( => AnsiString )' jetzt UnicodeString. Hängt mit der Umstellung auf Unicode zusammen.
In MessageBoxen casten:UnicodeString( "irgendein Text" ).w_str();
z. B.
grüssle
-
Danke dir.
Ich habe mich etwas schlau gemacht. Leider betrifft das so einiege Stringvariablen bei mir.
Ich denke, dass ich alle AnsiString deklarierten variablen in UnicodeString delkarierte Variabelen abändern werde. Damit passe ich die Texte auch gleich für andere Schriften an.Eine weitere Frage hätte ich noch. Erwarten die Steuerelemente eigentlich nun einen UnicodeString? Wenn nein, wie kann ich den Steuerelementen Chinesische oder Arabische Schriftzeichen zuweisen?
-
schau mal hier
-
radian schrieb:
Meine Frage an euch ist, warum diese Änderung nun im Builder 2009 integriert wurden.
Eine der häufigsten Nachfragen bei der Planung für künftige Produktversionen war, wann die VCL denn endlich Unicode unterstütze; aus diesem Grund - auch zugunsten besserer Kompatibilität zu Windows NT, das intern ohnehin Unicode verwendet - ist die VCL von Delphi und C++Builder 2009 komplett Unicode-basiert. Der Delphi-Datentyp 'string' ist nun ein Alias für UnicodeString, ein nativer, referenzgezählter UTF-16-String (der im Übrigen mit dem COM-String-Wrapper WideString nichts zu tun hat).
Es gibt wie bisher den Typen AnsiString, der herkömmlichen Strings mit der System-Codepage verwaltet, aber der Standard-String-Typ ist UnicodeString, und wie Allen Bauer hier ausführlich begründete, kann man das auch nicht ändern.
Analoges gilt auch für das typedef System::String - das allerdings so gut wie kein C++Builder-Programmierer direkt benutzt hat; gewöhnlich wird direkt AnsiString referenziert. Das kann von Vorteil und von Nachteil sein.
Alle Strings aus Delphi-Units, z.B. alle Properties einer Komponente vom Typ String, sind nun natürlich UnicodeStrings.Die Portierung von Delphi-Code nach Delphi 2009 kann aufgrund dieser Umstellung vereinzelt ein wenig Nachbearbeitung erfordern, doch hat man am Ende dieses Umstellungsprozesses - sofern man nicht ganz platt jedes Vorkommen von 'string' durch 'AnsiString' ersetzt, sondern nur bei Bedarf nachgearbeitet hat - eine komplett Unicode-taugliche Anwendung. In C++Builder sieht das etwas anders aus.
Von vielen Windows-Funktionen, die einen String entgegennehmen, gibt es eine mit A oder W suffizierte Version. Unter Windows 9x/ME waren die meisten *W-Funktionen Stubs, die nur einen Fehlercode zurückgaben; hingegen sind unter Windows NT praktisch alle *A-Funktionen schlichte Wrapper, die das Argument nach Unicode (UCS-2 bis NT 4, UTF-16 ab Windows 2000) konvertieren, dann die *W-Variante aufrufen und ggf. Rückgabewerte wieder in die System-Codepage umwandeln.
Offensichtlich entsteht dadurch ein gewisser Performance-Overhead, den man vermeiden kann, wenn man die *W-Varianten benutzt (dann kann das Programm allerdings nicht mehr unter Windows 9x/ME ausgeführt werden). Dieser Umstand ist mitverantwortlich dafür, daß die String-Performance in Delphi 2009 etwa so gut ist wie in Delphi 2007, obwohl alle Strings nun etwa doppelt so lange sind.In Delphi wird jeder direkte Aufruf einer WinAPI-Funktion ab Delphi 2009 die *W-Version verwenden. In C++Builder hängt das von der "_TCHAR"-Option in "Projektoptionen...|Verzeichnisse und Bedingungen" ab (wie unter ms-help://embarcadero.rs2009/devwin32/tcharmapping_xml.html dokumentiert): ist diese auf "char" gestellt, so ist weder UNICODE noch _UNICODE definiert, und alle WinAPI-Funktionsaufrufe verwenden die *A-Variante. Ist sie auf "wchar_t" gestellt, dann definiert die IDE beide Symbole für alle Dateien des Projekts; infolgedessen werden alle WinAPI-Funktionsaufrufe auf die *W-Variante umgeleitet.
Damit eine C++Builder-Anwendung Unicode-fähig sein kann, müssen die *W-Varianten benutzt werden, um dem Datenverlust durch die Codepage-Umwandlung vorzubeugen. Daher sollte dieser Switch für VCL-Anwendungen auf "wchar_t" gestellt werden.Weiter ist der Delphi-Compiler intelligent genug, um Stringliterale je nach Kontext als ANSI- oder als Unicode-Literal aufzufassen. Der C++-Compiler darf das gar nicht, denn der Standard schreibt vor, daß zwischen beiden explizit unterschieden werden müsse:
// "foo" ist ein ANSI-Literal vom Typ const char*: const char* foo = "foo"; // L"bar" ist ein Unicode-Literal vom Typ const wchar_t*: const wchar_t* bar = L"bar";
Erwartungsgemäß scheitern Zuweisungen, wenn die Zeigertypen nicht übereinstimmen.
Ein kleines Detail in der RTM-Version von C++Builder 2009 will diese Sache vereinfachen:
// ustring.h, Z. 144ff // C string operator. // NOTE: c_str() returns 'char*' by default to be compatible with // AnsiString::c_str(). However, note that it does so by narrowing // the underlying data of the UnicodeString. So, it's dangerous // to use 'char* UnicodeString::c_str()' in cases where the // the owner of the UnicodeString does not expect its underlying // data to change. #if !defined(USTRING_CSTR_AS_WCHART) // Hack (apparently) for backward compatibility with AnsiString ?? char* __fastcall c_str(); #else wchar_t* __fastcall c_str() const { return (Data)? Data: const_cast<wchar_t*>(L"");} #endif
Man beachte bitte, welch tiefgreifende Prävention nötig ist, um so etwas zu ermöglichen. Der eigentliche Anlaß für diese Prävention war zwar ein anderer - nämlich ist es mit DFM-Dateien möglich, die Typüberprüfung von Event-Handlern zu umgehen und Event-Handler mit AnsiString-Parametern an Events mit UnicodeString-Parametern zuzuweisen -, aber infolgedessen muß nun jeder UnicodeString damit rechnen, daß er möglicherweise ein AnsiString ist (und umgekehrt), und das vor jedem Direktzugriff auf die Daten überprüfen. Eine ausführliche Diskussion dieses Umstandes ist hier zu finden.
Da es massive Beschwerden von Benutzern über das Verhalten von UnicodeString::c_str() gab, kann es allerdings gut sein, daß diese Krücke im Update 1 entfernt wird und UnicodeString::c_str() so aussieht:
wchar_t* __fastcall c_str() const { return (Data)? Data: const_cast<wchar_t*>(L"");}
Das führt zwar zu einigen zusätzlichen Compilerfehlern, sollte aber insgesamt die Codequalität verbessern. Für die RTM-Version ist es, wie oben zu sehen, möglich, diese Variante über das Definieren des USTRING_CSTR_AS_WCHART-Makros in den Projektoptionen zu aktivieren.
Wir wollen im weiteren davon ausgehen, daß UnicodeString::c_str() den Rückgabewert wchar_t* hat (sei es durch die Definition von USTRING_CSTR_AS_WCHART, sei es durch eine mögliche Änderung seitens CodeGears). Dann verhält sich UnicodeString::c_str() analog zu std::wstring::c_str().
Ein typisches, aber stark vereinfachtes C++Builder-Szenario könnte so aussehen:
#include <vcl.h> #include <cstring> #include <cstdio> #include <string> #pragma hdrstop // ... void __fastcall TForm1::Button1Click(TObject *Sender) { AnsiString someString = "My Message"; MessageBox (Handle, someString.c_str (), Application->Title.c_str (), MB_OK); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { std::string s = "Button1 Button2"; const char* ps; char buf[300]; // Drohender Buffer-Overflow - aber das ist ein anderes Thema. TButton* BtnSender = &dynamic_cast <TButton&> (*Sender); ps = std::strstr (s.c_str (), BtnSender->Caption.c_str ()); std::sprintf (buf, "Sender button: %s\nValid: %s", BtnSender->Caption.c_str (), (ps != 0) ? "true" : "false"); MessageBox (Handle, buf, "Some title", MB_OK); }
In der RTM-Version von C++Builder 2009 wird das anstandslos kompiliert - mit nicht ungefährlichen Folgen. Wir begeben uns daher in die Projektoptionen definieren USTRING_CSTR_AS_WCHART und stellen den erwähnten Switch auf "wchar_t".
Nun erhalten wir einige Fehlermeldungen. Im ersten Fall sind diese einfach zu bekämpfen:
// E2034 Konvertierung von 'char *' nach 'const wchar_t *' nicht möglich // E2342 Keine Übereinstimmung des Typs beim Parameter 'lpText' ('const wchar_t *' erwartet, 'char *' erhalten) void __fastcall TForm1::Button1Click(TObject *Sender) { // Zur Lösung entweder someString als String und nicht als AnsiString deklarieren: //AnsiString someString = "My Message"; String someString = _T ("My Message"); // ...oder aber den zweiten Parameter explizit nach String casten: //MessageBox (Handle, someString.c_str (), Application->Title.c_str (), MB_OK); MessageBox (Handle, String (someString).c_str (), Application->Title.c_str (), MB_OK); }
Etwas schwieriger ist die zweite Funktion:
void __fastcall TForm1::Button2Click(TObject *Sender) { std::string s = "Button1 Button2"; const char* ps; char buf[300]; TButton* BtnSender = &dynamic_cast <TButton&> (*Sender); // E2285 Keine Übereinstimmung für 'std::strstr(const char *,wchar_t *)' gefunden ps = std::strstr (s.c_str (), BtnSender->Caption.c_str ()); std::sprintf (buf, "Sender button: %s\nValid: %s", BtnSender->Caption.c_str (), (ps != 0) ? "true" : "false"); // E2034 Konvertierung von 'char *' nach 'const wchar_t *' nicht möglich // E2342 Keine Übereinstimmung des Typs beim Parameter 'lpText' ('const wchar_t *' erwartet, 'char *' erhalten) MessageBox (Handle, buf, "", MB_OK); }
Man könnte hier BtnSender->Caption zunächst nach AnsiString casten und dann dessen c_str()-Operator aufrufen, beim MessageBox-Aufruf buf explizit nach String casten und das Literal mit _T() umgeben. Doch das Programm verhält sich nicht wie erwartet:
Button2Click schrieb:
---------------------------
Some title
---------------------------
Sender button: B
Valid: true
---------------------------
OK
---------------------------Das Problem ist in std::sprintf, dessen Format-Spezifikation nicht mehr mit den Parametern übereinstimmt: der dritte Parameter ist ein wchar_t*, sprintf erwartet aber ein char*. (Und dies ist die glücklichere Variante: falls das Mißverständnis andersherum ist, sind die Chancen, in einer AV zu enden, sehr groß.)
Unser Code benutzt nun eine Mischung aus char und wchar_t; das ist ineffizient und ggf. verlustbehaftet (Umwandlungen), fehleranfällig (sprintf etc.), und schwer zu warten. Daher wollen wir einheitlich wchar_t verwenden.
Dazu gibt es im Header <tchar.h> einige nützliche Makros, die uns dabei assistieren können. Konkret sieht der konvertierte Code so aus:
#include <tchar.h> void __fastcall TForm1::Button2Click(TObject *Sender) { std::basic_string <_TCHAR> s // _TCHAR anstatt von char verwenden = _T ("Button1 Button2"); // alle Literale mit _T () umgeben... const _TCHAR* ps; _TCHAR buf[300]; TButton* BtnSender = &dynamic_cast <TButton&> (*Sender); // anstelle der str*-Funktionen die _tcs*-Makros benutzen ps = std::_tcsstr (s.c_str (), BtnSender->Caption.c_str ()); // derartige Makros gibt es für fast alle C-Funktionen, die char*- // Parameter erwarten. Bei Interesse findet sich in <tchar.h> eine // vollständige Auflistung ;) std::_stprintf (buf, _T ("Sender button: %s\nValid: %s"), BtnSender->Caption, (ps != 0) ? _T ("true") : _T ("false")); MessageBox (Handle, buf, _T ("Some title"), MB_OK); }
Das vermeidet Ineffizienzen und Datenverlust. Es ist zwar nicht äußerst schön anzusehen, hat aber den großen Vorteil, daß es auch mit älteren C++Builder-Versionen einwandfrei übersetzt wird.
Wer lieber eine etwas schönere Version hätte und auf ältere C++Builder-Versionen keine Rücksicht nehmen muß, kann auch explizit wchar_t verwenden:
void __fastcall TForm1::Button2Click(TObject *Sender) { std::wstring s = L"Button1 Button2"; const wchar_t* ps; wchar_t buf[300]; TButton* BtnSender = &dynamic_cast <TButton&> (*Sender); ps = std::wcsstr (s.c_str (), BtnSender->Caption.c_str ()); std::swprintf (buf, L"Sender button: %s\nValid: %s", BtnSender->Caption, (ps != 0) ? L"true" : L"false"); MessageBox (Handle, buf, L"Some title", MB_OK); }
Einzig verbleibendes Problem ist, daß die *printf- und *scanf-Funktionen nicht typsicher (und überdies anfällig für Buffer Overflows) sind, was die Fehleranfälligkeit erhöht. In C++Builder könnte man stattdessen die Format()-Funktion benutzen:
String buf2 = Format (_T ("Sender button: %s\nValid: %s"), ARRAYOFCONST ((BtnSender->Caption, (ps != 0) ? _T ("true") : _T ("false"))));
Wenn wir hier falsche Parameter übergeben, wirft die Funktion eine Exception.
-
audacia schrieb:
Ein kleines Detail in der RTM-Version von C++Builder 2009 will diese Sache vereinfachen:
// ustring.h, Z. 144ff // C string operator. // NOTE: c_str() returns 'char*' by default to be compatible with // AnsiString::c_str(). However, note that it does so by narrowing // the underlying data of the UnicodeString. So, it's dangerous // to use 'char* UnicodeString::c_str()' in cases where the // the owner of the UnicodeString does not expect its underlying // data to change. #if !defined(USTRING_CSTR_AS_WCHART) // Hack (apparently) for backward compatibility with AnsiString ?? char* __fastcall c_str(); #else wchar_t* __fastcall c_str() const { return (Data)? Data: const_cast<wchar_t*>(L"");} #endif
Man beachte bitte, welch tiefgreifende Prävention nötig ist, um so etwas zu ermöglichen. Der eigentliche Anlaß für diese Prävention war zwar ein anderer - nämlich ist es mit DFM-Dateien möglich, die Typüberprüfung von Event-Handlern zu umgehen und Event-Handler mit AnsiString-Parametern an Events mit UnicodeString-Parametern zuzuweisen -, aber infolgedessen muß nun jeder UnicodeString damit rechnen, daß er möglicherweise ein AnsiString ist (und umgekehrt), und das vor jedem Direktzugriff auf die Daten überprüfen. Eine ausführliche Diskussion dieses Umstandes ist hier zu finden.
Da es massive Beschwerden von Benutzern über das Verhalten von UnicodeString::c_str() gab, kann es allerdings gut sein, daß diese Krücke im Update 1 entfernt wird und UnicodeString::c_str() so aussieht:
wchar_t* __fastcall c_str() const { return (Data)? Data: const_cast<wchar_t*>(L"");}
Das führt zwar zu einigen zusätzlichen Compilerfehlern, sollte aber insgesamt die Codequalität verbessern. Für die RTM-Version ist es, wie oben zu sehen, möglich, diese Variante über das Definieren des USTRING_CSTR_AS_WCHART-Makros in den Projektoptionen zu aktivieren.
Wie zu erwarten wurde das Verhalten von UnicodeString::c_str() für das Update 1 geändert: es gibt nun immer wchar_t* zurück.
-
Hallo zusammen,
wie bekomme ich denn sowas umgebaut ? Komme mit der Erklärung oben nicht zurecht.
dwAttrs = GetFileAttributes( OpenDialog1->FileName.c_str() ); // so geht's auch nicht dwAttrs = GetFileAttributes( OpenDialog1->FileName.w_str() );
Konvertierung von wchar_t* nach const char * nicht möglich
Vielen Dank im voraus.
-
Nimm dir die Zeit und lies meinen Post.