concat PWSTR und Strings

StehtimSchilf

Erfahrenes Mitglied
Hi Forum

ich habe wohl für Euch ein einfaches Problem, doch ich steh wie ein Esel vor'm Berg. Ich habe Funktionen (nicht von mir geschrieben), welche 2 "Strings" vom Typ "PWSTR" zurückgeben.

PWSTR a = funcA();
PWSTR b = funcB();

Ich hab nun eine eigene Funktion geschrieben und möchte darin folgendes:

Code:
void myFunc(PWSTR a, PWSTR b) {

   LPCWSTR c = "Hallo " + a + " Welt " + b + "!"

}

die Variable c MUSS vom Typ LPCWSTR sein, denn ich muss diese dann wieder an einer anderen Funktion übergeben. Die Stringliterale "Hallo" und "Welt" sind hartkodiert (böse, ich weiss, aber derzeit, ist mein C++ ja noch zu mies). Wenn bspw. die PWSTR Variable a "klein" und b "blau" enthalten würde, so sollte dann in der LPCWSTR Variable c "Hallo klein Welt blau!" stehen.

Aber ich habe keine Ahnung wie ich Pointer mit String konkateniere. Mir ist klar, dass es nicht mit + geht, aber kann mir jemand genau dieses Problem lösen? Ich kann nur myFunc() anpassen und die c muss vom Typ LPCWSTR sein.

ich hoffe, ich konnte mich verständlich ausdrücken.

cheerioh & thx
SiS
 
Hi Matthias

leider habe ich keine (noch) Ahnung wovon Du sprichst :) sorry, aber 3 Bücher liegen schon bereit. ich möchte einfach eine kleine Anpassung machen (komm aus der Java, .NET Welt) an einem alten funktionierenden Code.

Ich hab nun mit x verschienden Forenbeiträgen folgendes zusammen geschustert:

Code:
void myFunc(PWSTR a) {
   USES_CONVERSION;
   std::string strA(W2A(a));
   std::string result = "prefix" + strA + "suffix";
}

Wie bös ist das?

Dann noch diese Frage, weil dies eigentlich der Ursprung meines Problems ist: Wann immer ich mir ein Beispielcode aus dem Netz runterlade und in mein Projekt kopiere, erhalte ich eben immer error C2664.

z.B. der simple Code hier.
error C2664: 'RegOpenKeyW': cannot convert parameter 2 from 'const char[50]' to 'LPCWSTR'

Ich habe unser bestehendes Projekt einfach im VS2500 geöffnet. Aber irgendwie scheint jeder andere Code aus dem iNet in dem Projekt nicht zu laufen. Vom Icon her im Solution Explorer ist es ein "Windows Forms Application" Projekt und wird eine .dll erstellt, welche ich dann ins system32 kopieren kann.

Aber mich wurmt es,warum ich bei allen Beispielen, immer irgendwas casten muss?

Danke für Ideen!


BTW: Mir ist bewusst, dass mir die C++ Grundlagen fehlen, aber da ursprüngliche Entwickler leider von dannen gezogen ist, hängt es an mir. Und ich möchte das Projekt nicht krass weiterentwickeln, sondern, dass nur die Werte, welche derzeit in einer INI stehen aus der registry gelesen werden. Dann kann es wieder für die nächsten 5j laufen. Und dafür möchte ich 1000 Seiten C++ lernen. Ich hoffe ihr versteht das!
 
Hi.
Code:
void myFunc(PWSTR a) {
   USES_CONVERSION;
   std::string strA(W2A(a));
   std::string result = "prefix" + strA + "suffix";
}

Wie bös ist das?
Du führst hier eine (verlustbehaftete) Konvertierung durch. D.h. Zeichen die sich in der aktuellen ANSI Codepage nicht darstellen lassen werden durch Fragezeichen ersetzt.

Du kannst einfach std::wstring verwenden.
Dann noch diese Frage, weil dies eigentlich der Ursprung meines Problems ist: Wann immer ich mir ein Beispielcode aus dem Netz runterlade und in mein Projekt kopiere, erhalte ich eben immer error C2664.

z.B. der simple Code hier.
error C2664: 'RegOpenKeyW': cannot convert parameter 2 from 'const char[50]' to 'LPCWSTR'
Das hängt damit zusammen, dass du in deinem Projekt UNICODE verwendest (siehe Projekteinstellungen).

Ist das Makro _UNICDODE bzw. UNICODE definiert, dann wird aus
C++:
RegOpenKey(...);
in Wirklichkeit:
C++:
RegOpenKeyW(...);
ansonsten wird daraus:
C++:
RegOpenKeyA(...);
Das gilt für beinahe alle Funktionen der Windows API die Zeichnketten als Argumente akzeptieren. Für jede dieser Funktionen gibt es 2 Versionen: die Wide character und die ANSI Funktion.

Es gibt mehrere Möglichkeiten:

  • Verwende einfach direkt die ANSI Funktion.
  • Schalte UNICODE aus.
  • Verwende generische Funktionen und das TEXT Makro:
C++:
// nicht so:
RegOpenKey("bla/blub/key", ...);
// sondern so:
RegOpenKey(TEXT("bla/blub/key"), ...);
Die 3. Option ist in der Regel die beste, so kann man zwischen ANSI und Unicode Modi wechseln.

Aber mich wurmt es,warum ich bei allen Beispielen, immer irgendwas casten muss?
Grundsätzlich: wenn du etwas castest und du weißt nicht warum nur um Compilerfehler wegzubekommen, dann ist das zu 99% falsch.

Gruß
 
Danke deepthroat für die prompte und ausführliche Antwort.

Bevor ich den Code umstelle, kurz noch die Frage: Du empfiehlst also in den Projekteinstellungen die "Character Set" auf "use Multi Byte Character Set" zu setzen und anschliessend mit Makros die Parameter an die Funktionen zu übergeben?

Thx
SiS
 
Danke deepthroat für die prompte und ausführliche Antwort.

Bevor ich den Code umstelle, kurz noch die Frage: Du empfiehlst also in den Projekteinstellungen die "Character Set" auf "use Multi Byte Character Set" zu setzen und anschliessend mit Makros die Parameter an die Funktionen zu übergeben?
Nein, ich würde den Unicode-Zeichensatz verwenden.

Und dann die generischen Funktionen aus tchar.h, also nicht strcat und nicht wcscat sondern _tcscat etc. verwenden. (siehe http://msdn.microsoft.com/en-us/library/ms860358.aspx)

Gruß
 
Zuletzt bearbeitet:
Hey super, danke! Ich konnte nun eigentlich alles "gebastel" ummodeln, ohne Compiler-fehler.

So, aber nun habe ich das interessante Problem, dass mir die String-Operationen nur jeweils ein Zeichen zurückliefern. Ich weiss, es wird wohl etwas off-topic. Aber warum? Ich kann nun den Code wie in den Foren herauskopieren, mit/ohne Makros versehen, kompilieren, fein! Doch bei mir kommt im String dann immer nur 1 Zeichen raus:confused:

Code:
	HKEY hKey;	
	DWORD dwData = 256;
	BYTE cDaten[256] = "";

	RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("bla\\bli\\bla\\blu"), &hKey);
	RegQueryValueEx(hKey, TEXT("VALUE"), 0, 0, cDaten, &dwData);
	RegCloseKey(hKey);
	std::cout << cDaten << std::endl;
	printf("result: %s\n", cDaten);
	std::cout << (char*) cDaten << std::endl;

Der Wert unter "VALUE" (REG_SZ) hat nur 5 Zeichen, aber dwData liefert mir 12 Egal ob ich es mit char, TCHAR etc. mache... ich bekomm immer nur das erste Zeichen? Wie von Dir empfohlen, ist das Projekt nun auf UNICODE und wie Du siehst, verwende ich die Makros... drum äää, steh ich auf'm Schlauch :)

Danke nochmals & cheerioh
SiS
 
Zuletzt bearbeitet:
Hey super, danke! Ich konnte nun eigentlich alles "gebastel" ummodeln, ohne Compiler-fehler.

So, aber nun habe ich das interessante Problem, dass mir die String-Operationen nur jeweils ein Zeichen zurückliefern. Ich weiss, es wird wohl etwas off-topic. Aber warum? Ich kann nun den Code wie in den Foren herauskopieren, mit/ohne Makros versehen, kompilieren, fein! Doch bei mir kommt im String dann immer nur 1 Zeichen raus:confused:

Code:
	HKEY hKey;	
	DWORD dwData = 256;
	BYTE cDaten[256] = "";

	RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("bla\\bli\\bla\\blu"), &hKey);
	RegQueryValueEx(hKey, TEXT("VALUE"), 0, 0, cDaten, &dwData);
	RegCloseKey(hKey);
	std::cout << cDaten << std::endl;
	printf("result: %s\n", cDaten);
	std::cout << (char*) cDaten << std::endl;

Der Wert unter "VALUE" (REG_SZ) hat nur 5 Zeichen, aber dwData liefert mir 12 Egal ob ich es mit char, TCHAR etc. mache... ich bekomm immer nur das erste Zeichen? Wie von Dir empfohlen, ist das Projekt nun auf UNICODE und wie Du siehst, verwende ich die Makros... drum äää, steh ich auf'm Schlauch :)
Intern speichert Windows alles im UTF-16 Unicode Encoding. Jedes Zeichen nimmt also 2 Byte ein. Ein Wort der Länge 5 benötigt also 10 Bytes. Jeder String muss in der Registry mit \0 terminiert sein, also muss dwData == 12 sein.

Du versuchst hier Daten im Unicode Format auf std::cout auszugeben, welches char-basiert arbeitet.

Nimm eine generische Textausgabefunktion wie z.B. _tprintf. \edit: Außerdem hast du auch die Daten ungünstig deklariert (sonst hätte der Compiler sich bei cout beschwert):
C++:
TCHAR cDaten[dwData / sizeof(TCHAR)];

RegQueryValueEx(hKey, TEXT("VALUE"), 0, 0, static_cast<LPVOID>(cDaten), &dwData);
oder einfach nur so:
C++:
LPCTSTR regString = reinterpret_cast<LPCTSTR>(cDaten);
std::wcout << regString << std::endl;
Auf jeden Fall solltest du darauf achten Daten immer richtig zu typisieren. Das Typsystem ist ja keine pure Gängelei vom Compiler sondern soll eine Hilfe sein...

Ansonsten, wenn du die Daten vernünftig auf der Konsole ausgeben willst, müßtest du die Daten vom UTF-16 in die aktuelle Codepage der Konsole kodieren.

Gruß
 
Zuletzt bearbeitet:
LPCTSTR und entsprechende String Funktionen

Also nochmals tausend Dank.

Dank Deinen ausführlichen Antworten und auch den Beispielen habe ich mir einige Interessante Artikel im Internet angeschaut. Auch der MSDN Link war echt hilfreich! Ich habe nun überall die Gebastel-Casts entfernt - dachte ich. Beim Suchen & Ersetzen habe ich nun gefunden, dass hier schon vor meinem Eingreifen gefummelt wurde beim Casten und zwar aus folgendem Grunde:

Viele Strings "stecken" in einem LPCTSTR. Vorhandene selber gschriebene Klassen für die Implementierung von right(), left(), substr() verwenden jedoch als Parameter char *.

Meine Frage nun an Dich:
Sollte ich nun alle Funktionen umschreiben, damit sie einen anderen Parametertyp verwenden? Z.B. die simple stringRight()-Funktion:
Code:
char stringRight(char *s) {
	return s[strlen(s)];
}

Oder kann man hier doch Bastelcasten? Ich hab in vielen ähnlichen posts gelesen, dass man in C++ eigentlich NICHT casten soll, also hab ich die Funktionen umgeschrieben und die nehmen nun TCHAR:

Bspw. diese hier:
Code:
LPCTSTR appendIfMissing(LPCTSTR lpString, TCHAR *s) {

	INT lenS;
	lenS = _tcsclen(lpString);
	std::wcout << "Länge: " << lenS << std::endl;

	// letzte Zeichen bestimmen
	LPCTSTR ss = SubString(lpString, lenS-1);
	std::wcout << "Sub String: " << ss << std::endl;

	// letztes Zeichen vergleichen
	LPCTSTR result;
	if (_tcscmp(ss, TEXT(";")) != 0) {
		// Suffix fehlt
		// LPCTSTR to TCHAR fuer _tcscat()
		TCHAR t1[512];
		_tcscpy_s(t1, lpString);

		// fehlendes ; anfuegen
		_tcscat_s(t1, TEXT(";"));
		t1[lenS+1] = '\0';
		std::wcout << "appended: " << t1 << std::endl;

		// zurueckschreiben in zielvariable
		result = (LPCTSTR) t1;
	} else {
		// Suffix vorhanden
		result = lpString;
	}

	return result;
}
// Gefunden im internet
LPCTSTR SubString(LPCTSTR lpExpression, // [in] Pointer to a string to extract the substring from
				  ULONG nStart, // [in] Starting point
				  LONG nLength) // [in] how far to go from the startig point, use -1 to go the end of lpExpression
{

	if( nLength < 0 )
		nLength = _tcslen(lpExpression) - nStart;

	ULONG nPos = 0;
	PTCHAR lpResult = new TCHAR[512];

	for( ULONG i = nStart; i < (ULONG)(nStart+nLength); i++ )
	{
		// put the current char from lpExpression into lpResult
		lpResult[nPos++] = lpExpression[i];
	}

	// add the null byte to lpResult;
	lpResult[nPos] = '\0';

	// return the substring
	return lpResult;
}

Die Funktion fügt ein ; ans Ende an, wenn keines vorhanden ist (ist noch nicht komplett angepasst, da ja das ; in der Funktion noch hardkodiert ist. Auf jeden Fall liefert in der Konsole bei "appended" !!noch!! den korrekten Output. Wenn ich aber in einer anderen Funktion nun diese Funktion aufrufe, dann krieg ich nicht mehr den ganzen Output, sondern, bspw. nur noch die ersten 50 Zeichen!
Code:
		LPCTSTR urls = reinterpret_cast<LPCTSTR>(cDaten);
		LPCTSTR result;
		result= appendIfMissing(urls , TEXT(";"));
		std::wcout << "URLs mit ;: " << result<< std::endl;
		// output enstpricht nicht output in appendIfMissing()

Kopiere ich jedoch den gesamten Funktionsinhalt direkt in meine aufzurufende Funktion, dann klappt es wunderschön. Je nachdem ob ein ; am Ende ist, wird eines angefügt oder eben nicht.

Heisst das nun, dass ich LPCTSTR nicht analog char* an Funktionen übergeben und zurückgeben kann? M.E. habe ich hier nun keine Bastelcasts mehr, oder?

Das wäre dann wirklich das letzte Problem, was ich mit Casten von LPCTSTR, TCHAR, PWSTR habe - hoff' ich!

Kasten Bier und Steaks sind als Dank schon bestellt! :)

cheerioh & thx
SiS
 
Hi.
Meine Frage nun an Dich:
Sollte ich nun alle Funktionen umschreiben, damit sie einen anderen Parametertyp verwenden? Z.B. die simple stringRight()-Funktion:
Code:
char stringRight(char *s) {
	return s[strlen(s)];
}

Oder kann man hier doch Bastelcasten?
Nein, durch casten veränderst du zwar den Typ aber nicht die Daten die dahinterstecken.
Bspw. diese hier:
Code:
LPCTSTR appendIfMissing(LPCTSTR lpString, TCHAR *s) {

	INT lenS;
	lenS = _tcsclen(lpString);
	std::wcout << "Länge: " << lenS << std::endl;

	// letzte Zeichen bestimmen
	LPCTSTR ss = SubString(lpString, lenS-1);
	std::wcout << "Sub String: " << ss << std::endl;

	// letztes Zeichen vergleichen
	LPCTSTR result;
	if (_tcscmp(ss, TEXT(";")) != 0) {
		// Suffix fehlt
		// LPCTSTR to TCHAR fuer _tcscat()
		TCHAR t1[512];
		_tcscpy_s(t1, lpString);

		// fehlendes ; anfuegen
		_tcscat_s(t1, TEXT(";"));
		t1[lenS+1] = '\0';
		std::wcout << "appended: " << t1 << std::endl;

		// zurueckschreiben in zielvariable
		result = (LPCTSTR) t1;
	} else {
		// Suffix vorhanden
		result = lpString;
	}

	return result;
}
// Gefunden im internet
LPCTSTR SubString(LPCTSTR lpExpression, // [in] Pointer to a string to extract the substring from
				  ULONG nStart, // [in] Starting point
				  LONG nLength) // [in] how far to go from the startig point, use -1 to go the end of lpExpression
{

	if( nLength < 0 )
		nLength = _tcslen(lpExpression) - nStart;

	ULONG nPos = 0;
	PTCHAR lpResult = new TCHAR[512];

	for( ULONG i = nStart; i < (ULONG)(nStart+nLength); i++ )
	{
		// put the current char from lpExpression into lpResult
		lpResult[nPos++] = lpExpression[i];
	}

	// add the null byte to lpResult;
	lpResult[nPos] = '\0';

	// return the substring
	return lpResult;
}
Diese Funktionen sind beide problematisch - und zu kompliziert.

Sie geben beiden einen LPCTSTR, also einen konstanten String zurück. Damit wird signalisiert, das der Speicher für den String innerhalb der Funktion verwaltet wird. Für die SubString Funktion ist das eine glatte Lüge, denn sie gibt einen neu allozierten String zurück den der Aufrufer dann eigentlich mit delete[] wegräumen müßte damit ein Speicherleck vermieden wird.

In der appendIfMissing Funktion hingegen wird versucht den Speicher zu verwalten, allerdings gibst du einen Zeiger auf eine automatische Variable, welche auf dem Stack liegt zuück und diese wird noch vor dem Rücksprung an den Aufrufer wieder weggeräumt. Daraus resultiert undefiniertes verhalten, da du dann auf einen Zeiger zugreifst der gar nicht mehr gültig ist.

Wenn du schon C++ hast, dann benutze doch einfach generell die STL Strings.
C++:
std::wstring& appendIfMissing(std::wstring& str, TCHAR ch) {
  if (str.empty() || *str.rbegin() != ch) {
    str += ch;
  }
  return str;
}
Oder für Strings:
C++:
std::wstring& appendIfMissing(std::wstring& str, const std::wstring& suffix) {
  if (str.length() < suffix.length()
      || str.compare(str.length() - suffix.length(), suffix.length(), suffix) != 0)
  {
    str += suffix;
  }
  return str;
}
Du kannst das natürlich auch im C-Stil machen, du müßtest dafür nur immer Speicher reservieren und dann im aufrufenden Teil wieder freigeben. Oder du reservierst den Speicher im aufrufenden Programmteil und übergibst der Funktion Quellstring und Zielstring. Und das Kopieren kannst du doch mit der _tscpy Funktion machen, da mußt du doch nicht selbst eine Schleife schreiben.

Gruß
 
Zuletzt bearbeitet:
Zurück