Narrowing unicode

Hi all,

I got a problem trying to trace unicode string in a file.
My trace class is ascii, not unicode. I don't really care : at tracing time, I just convert from unicode to ascii.
Well... it seems not to work at all. Though, the code is not from me, it's from some forum, and it looked pretty good to me.


The calling part is here :
1
2
3
4
5
6
7
8
9
10
std::wstring text;

hWndEdit = CreateWindowExW (WS_EX_CLIENTEDGE, TEXT("Edit"), _T("that's a test"),
									   WS_CHILD | WS_VISIBLE | ES_MULTILINE |ES_AUTOHSCROLL | ES_AUTOVSCROLL, 100, 20, 340, 80,
									   hWnd, (HMENU)IDC_TEXT_EDIT, NULL, NULL);

(...)

getText(hWndEdit, &text);
TRACE(CTrace::NIV_DEB, "["<<narrow(text.c_str())<<"]");


The getText & narrow fonctions are here :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
std::wstring* getText(HWND handle, std::wstring *text)
{
	int len = SendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
    wchar_t* buffer = new wchar_t[len+1];
    SendMessage(handle, WM_GETTEXT, (WPARAM)len+1, (LPARAM)buffer);

	std::wstring temp(buffer);

	*text = temp;

	return text;
}


std::string narrow( const std::wstring& str )
{
	std::ostringstream stm ;
	const std::ctype<char>& ctfacet =
	std::use_facet< std::ctype<char> >( stm.getloc() ) ;
	for( size_t i=0 ; i<str.size() ; ++i )
	stm << ctfacet.narrow( str[i], 0 ) ;
	return stm.str() ;
}


The text in the edit is "that's a test"
The resulting in the file is :
DEBU;1e4c;121024 002428;Melodizer.cpp          167;WndProc                       ;> [F]


The text in the edit is "FFFAAA"
The resulting in the file is :
DEBU;1210;121024 002938;Melodizer.cpp          167;WndProc                       ;> [LFFA¨÷s


About the trace class, I've been using it for a long time, it's been working fine. So I guess the problem is before that, maybe during the conversion from unicode to 1 byte. I have no idea.

Thank you for helping !
closed account (DSLq5Di1)
#include <locale>

1
2
3
4
5
6
7
std::string narrow(const std::wstring& ws)
{
	std::string s(ws.length(), 0);
	std::use_facet<std::ctype<wchar_t> >(std::locale("")).narrow
		(ws.c_str(), ws.c_str() + ws.length(), 'X', &s[0]);
	return s;
}

Adapted from the example on MSDN @ http://msdn.microsoft.com/en-us/library/145y2358
Hm... it doesn't change anything, I get the same logs with the same text in the edit ><
Now I'm trying to change my trace class to wstring, maybe I should have started there.
To convert from ANSI to UNICODE use MultiByteToWideChar() API or mbstowcs, _mbstowcs_l from CRT.

http://msdn.microsoft.com/en-us/library/dd319072%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/k1f9b8cy%28v=vs.100%29.aspx

You have code example on MSDN webpage linked.
I would recommend you to restructure your whole code so that it strictly uses WINAPI data types for the sake of simplicity and portability,

all of API data types are defined in WinNT.h, WinDef.h and WinTsd.h
closed account (DSLq5Di1)
Patlegroin wrote:
Now I'm trying to change my trace class to wstring, maybe I should have started there.

That may be best. I've had no luck reproducing the issue from the snippets you've provided.

You should also be wary of using wide functions with TCHARs, as in CreateWindowExW( TEXT("Edit"), ... ), either call CreateWindowEx() and work with TCHARs, or use the wide function with a wide string literal L"Edit", don't mix and match.

If you need any help with your trace class, feel free to post the details.
@modoran : I first tried to convert unicode to ansi, now I'm trying to rewrite my class to manipulate wstring instead of string. I don't have need for ansi to unicode so far. Maybe I will, so I note that down. Thank you.

@codekiddy : I'm quite confused with what is standard type, what is not. My goal is to be the most simple, but when I search some help on internet, sometimes I try to use method using exotic types... I will check the files you said and see what I can keep as types. Thank you.

@sloppy9 : you're right, I'm now changing my string constants to TCHARS. I want to use unicode from basement to ceiling. Thank you.

I quickly get confused with all the differents types having the same purposes(from my comprehension), like wchar_t, TCHAR, WCHAR, etc.
Last edited on
Well, if you want to convert unicode to ansi use WideCharToMultiByte() or wcstombs from CRT.
http://msdn.microsoft.com/en-us/library/dd374130%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/5d7tc9zw%28v=vs.100%29.aspx
Thanks for helping.
I tried wcstombs, I had no success with it, but I don't rememer what was wrong. Maybe I will make another try.

And about WideCharToMultiByte()... it's just... wow...
1
2
3
4
5
6
7
8
9
10
 int WideCharToMultiByte(
  _In_       UINT CodePage,
  _In_       DWORD dwFlags,
  _In_       LPCWSTR lpWideCharStr,
  _In_       int cchWideChar,
  _Out_opt_  LPSTR lpMultiByteStr,
  _In_       int cbMultiByte,
  _In_opt_   LPCSTR lpDefaultChar,
  _Out_opt_  LPBOOL lpUsedDefaultChar
);


8 parameters ? Sorry, but I'm used to atoi or itoa, at worst 3 parameters. That just seems crazy to me. Like breaking a nut with a drop hammer or something. I will try it if someone put a gun on my head.

Now I'm trying to use unicode everywhere, instead of converting, if it's possible(I mean if every method exists in unicode form).

I have problem compiling my class, cause there is a macro inside it. And it seem to be the problem. I don't know how to solve that kind of thing, since there is no type in a macro.



Here is the trace.h file :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//==============================================================================
//	trace.h
//------------------------------------------------------------------------------
//	Creation :
//------------------------------------------------------------------------------
//	Description :
//		Declaration de la classe CTrace
//		(traces dans un fichier)
//------------------------------------------------------------------------------
//	Intervention(s) :
//		neant
//==============================================================================
#ifndef	_TRACE_MM_H
#define _TRACE_MM_H
#pragma once

#include <string>
#include <strstream>
#include <tchar.h>
#include "Fichier.h"

#define TRACE(NIVEAU, MESSAGE)		\
{									\
std::wstring file(__FILE__);	\
std::wstring function(__FUNCTION__);	\
std::ostrstream os;		\
os << MESSAGE << std::ends;	\
CTrace::_trace(NIVEAU, file, function, __LINE__, os); \
os.freeze(0); \
}

class CTrace
{
private:
	static long compteur; // Numero de ligne
	static long fileSize;
	/* Nom du fichier de traces */
	static std::wstring nom_fichier_traces;
	
	// Parametres lus dans le fichier d'initialisation
	static std::wstring initValue;

	static long FICHIER_TRACES_TAILLE;
	static long NB_FICHIERS_TRACES;

	/* Niveaux de traces activee */
	static long _niveaux;

	// strncpy qui pad la chaine de caractere avec des espaces si la source est moins longue que maxLen
	static void stringCopyAvecEspaces(std::wstring &dest, const std::wstring &source, short maxLen);

public:	
	static void Init(const std::wstring &nomProcess);

	// CTrace dans un fichier le message passee en paramtere a la printf
	static void _trace(const long niveau, const std::wstring &fichierSource, const std::wstring &fonction, int ligne, std::wstringstream & message);

	// Niveaux de traces
	static const short int NIV_INFO   	= 0x0001;	// Niveau 1 : traces importantes
	static const short int NIV_DEBUG	= 0x0002;	// Niveau 2 : traces moins importantes que le niveau 1
/*	static const short int NIV_03		= 0x0010;	// Inutilisees pour le moment
	static const short int NIV_04		= 0x0020;	// Inutilisees pour le moment
	static const short int NIV_05		= 0x0040;	// Inutilisees pour le moment*/
	static const short int NIV_ERR		= 0x1001;	// Trace d'erreur (NIV_INFO + 0x2000)
	static const short int NIV_DEB		= 0x2002;	// Trace de debut de fonction (NIV_DEBUG + 0x4000)
	static const short int NIV_FIN		= 0x4002;	// Trace de fin de fonction (NIV_DEBUG + 0x8000)

	static std::wstring getInitValues(){return initValue;}
};

#define OK					0 // Tout s'est bien passé ?
#define KO					1 // Un erreur est survenue
#define OK_FIN_TRAITEMENT	2 // Tout s'est bien pass?mais on arrête le traitement(ex : pas de donnees)

#endif 



Here is the trace.cpp file :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//==============================================================================
//	trace.cpp
//------------------------------------------------------------------------------
//	Création :
//------------------------------------------------------------------------------
//	Description :
//		Définition de la classe CTrace
//		(traces dans un fichier)
//------------------------------------------------------------------------------
//	Intervention(s) :
//		néant
//==============================================================================
#include <time.h>
#include <sys/stat.h>
#include <iostream>
#include <iomanip>
#include <windows.h>




// Gestion des traces
#include "Trace.h"

// Initialisation du compteur de traces
long CTrace::compteur = 0;
long CTrace::fileSize = 0;
std::wstring CTrace::nom_fichier_traces = _T("");
long CTrace::_niveaux = 0;
long CTrace::FICHIER_TRACES_TAILLE = 0;
long CTrace::NB_FICHIERS_TRACES = 0;
std::wstring CTrace::initValue = _T("");

// On passe le nom du process tel qu'il est écrit dans le fichier de config des traces.
// Ce nom sera également celui utilis?pour le fichier de trace du process.
void CTrace::Init(const std::wstring &nomProcess)
{
	nom_fichier_traces = nomProcess + _T(".log");

	// Fichier de traces du process
	CFichier file(nom_fichier_traces);

	// Fichier de configuration des traces
	CFichier fichierConfig(_T("traces.config"), std::ios::in);

	std::wstring parametres[3];					// Paramètres lus dans le fichier de configuration
	fileSize = file.size();						// Taille du fichier de traces du process ?l'ouverture

	parametres[0] = _T("1");	// Niveau de trace				(par défaut juste les traces de niveau 1)
	parametres[1] = _T("10");	// Nombre de fichiers de trace	(par défaut 10)
	parametres[2] = _T("1000");	// Taille d'un fichier de trace (par défaut 1000ko)

	// Récupération des paramètres de trace du process(taille d'un fichier, nb de fichiers, niveaux)
//	if(fichierConfig.is_open())
	{
		// Si le fichier n'est pas vide, on le parse
		if(fichierConfig.size() > 0)
		{
			// La structure d'une ligne est la suivante : NomDuProcess:<niv>/<nbFichiers>/<taille>
			// Il s'agit donc de retrouver la chaine NomDuProcess, puis de récupérer les 3 paramètres
			fichierConfig.read();
			std::wstring contenu = fichierConfig.getBuffer();
			size_t offsetDebut = contenu.find(nomProcess);

			offsetDebut+=nomProcess.length()+1; // On ajoute la longeur du nom de fichier pour se positionner ?la fin du nom,
			// puis on ajoute 1 pour "sauter" le caractère ':'
			size_t offsetFin = contenu.find('/', offsetDebut); // On cherche le premier / car il délimite le paramètre

			parametres[0] = contenu.substr(offsetDebut, offsetFin - offsetDebut);

			offsetDebut = offsetFin + 1; // L'offsetDebut suivant se situe juste après l'offsetFin actuel
			offsetFin = contenu.find('/', offsetDebut); // On cherche le premier / car il délimite le paramètre
			parametres[1] = contenu.substr(offsetDebut, offsetFin - offsetDebut);

			offsetDebut = offsetFin + 1; // L'offsetDebut suivant se situe juste après l'offsetFin actuel

			offsetFin = contenu.find('\n', offsetDebut); // On cherche le premier / car il délimite le paramètre
			parametres[2] = contenu.substr(offsetDebut, offsetFin - offsetDebut);
		}
	}

	switch(_wtol(parametres[0].c_str()))
	{
		case 0:
			_niveaux = 0;
			break;
		case 1:
			_niveaux = NIV_INFO|NIV_DEBUG; //|NIV_03|NIV_04|NIV_05;
			break;
		case 2:
			_niveaux = NIV_INFO;
			break;
		default:
			_niveaux = NIV_INFO;
			break;
	}
	NB_FICHIERS_TRACES		= _wtol(parametres[1].c_str());
	FICHIER_TRACES_TAILLE	= 1000*_wtol(parametres[2].c_str()); // x1000 parce que c'est exprim?en Ko

	if(FICHIER_TRACES_TAILLE <= 0)
	{
		FICHIER_TRACES_TAILLE = 1000000; // En cas de valeur ?la con on met 1Mo ?la place
	}
	if(NB_FICHIERS_TRACES <= 0)
	{
		NB_FICHIERS_TRACES = 7; // En cas de valeur ?la con on met 10 ?la place
	}

	// On renseigne init value ?des fins informatives pour l'utilisateur
	initValue = parametres[0];
	initValue += _T("/");
	initValue += parametres[1];
	initValue += _T("/");
	initValue += parametres[2];
}

//  Trace dans un fichier l'entete pass?en paramètre, ?la printf.
//  On ouvre et on ferme le fichier de trace ?chaque appel de la fonction,
//  sinon on perd toutes les traces en cas de crash
void CTrace::_trace(const long niveau, const std::wstring &fichierSource, const std::wstring &fonction, int ligne, std::wstringstream &message)
{
	// Si le niveau de trace n'est pas actif on sort de l?
	if(!(niveau & _niveaux))
	{
		return;
	}

	const wchar_t separateur = _T(';');

	// Si on fleurte avec FICHIER_TRACES_TAILLE, on sauvegarde le fichier courant
	// puis on crée un nouveau fichier
	if (FICHIER_TRACES_TAILLE < fileSize)
	{
		// Compteur de fichiers de log
		static int counter=0;
		
		// On rezérote la taille calculée du fichier de trace
		fileSize = 0;

		if(NB_FICHIERS_TRACES <= ++counter)
		{
			counter = 1;
		}
		// Construction du nom de fichier "ancien"(celui qu'on archive)
		std::wstringstream wss;
		wss << counter << std::ends;

		std::wstring nomFichierOld = nom_fichier_traces+_T(".")+wss.str();
		//wss. freeze(0);
		// Effacement de l'ancien fichier s'il existe
//		remove(nomFichierOld.c_str());
		// On renomme le fichier en cours en lui donnant le nom du fichier archiv?.old)
//		rename(nom_fichier_traces.c_str(), nomFichierOld.c_str());
	}
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
	// On renseigne l'entete
	std::wstring entete(_T("")); // numéro de ligne de code, fonction, date, etc.

	// Ajout du niveau de trace
	switch(niveau)
	{
		case NIV_INFO:
			entete+=_T("INFO");
			break;
		case NIV_DEBUG:
			entete+=_T("DBUG");
			break;
		case NIV_ERR:
			entete+=_T("ERR ");
			break;
		case NIV_DEB:
			entete+=_T("DEBU");
			break;
		case NIV_FIN:
			entete+=_T("FIN ");
			break;
		default:
			entete+=_T("    ");
			break;
	}

	entete+=separateur;

	{
		wchar_t tid[128];
		const short tidSize = 4;
		wchar_t tempTid[tidSize+1];
		wsprintf(tid, _T("%04x"), GetCurrentThreadId());
		for(int i=0 ; i<tidSize ; i++) {tempTid[i] = tid[i];}
		tempTid[tidSize]=0;
		entete +=tempTid;
	}

	entete+=separateur;

	// On récupère l'heure
	__time64_t ltime;
	_time64( &ltime );

    /* Use time structure to build a customized time wstring. */
    struct tm *today = _localtime64( &ltime );

    /* Use strftime to build a customized time wstring. */
	char tmpbuf[14];
    strftime( tmpbuf, 128, "%y%m%d %H%M%S\0", today);

    // Ajout de l'heure dans l'entete
    // Convert to a wchar_t*
    const size_t origsize = strlen(tmpbuf) + 1;
    size_t convertedChars = 0;
    wchar_t *wcstring = new wchar_t[origsize+1];
    mbstowcs_s(&convertedChars, wcstring, origsize, tmpbuf, _TRUNCATE);
	entete+=wcstring;
	if(nullptr != wcstring) delete [] wcstring;


	// Ajout du séparateur
	entete += separateur;

	// Ajout des nom du fichier source et numéro de ligne
	{
		const short FSLEN = 20; // Taille max d'un nom de fichier source
		const short NFLEN = 30; // Taille max d'un nom de fonction

		std::wstring nomFichierSource;
		stringCopyAvecEspaces(nomFichierSource, fichierSource.substr(1+fichierSource.find_last_of(_T("\\"))), FSLEN);

		wchar_t numeroLigne[8]; // Un espace, 5 chiffres, le 0 de fin de chaîne
		wsprintf(numeroLigne, _T(" %5d\0"), ligne);

		std::wstring nomFonction;
		stringCopyAvecEspaces(nomFonction, fonction, NFLEN);

		std::wstringstream wss;
		wss<<nomFichierSource<<numeroLigne<<separateur<<nomFonction<<separateur<< std::ends;

		entete += wss.str();
//		wss.freeze(0);
	}

	// Traces d'entrée et de sortie de fonction le cas échéant
	if(niveau == NIV_DEB) {
		entete +=_T("> ");
	}
	else if (niveau == NIV_FIN) {
		entete +=_T("< ");
	}

	// On écrit dans le fichier l'en-tête, le corps de la trace(entete) et on saute une ligne(veinarde)
	std::wstring trace = entete;
	if(niveau == NIV_ERR)
	{
		trace += _T("***ERREUR*** ");
	}
	trace += message.str();
//	message.freeze(0);
	fileSize+=trace.length();


	CFichier fichier_traces(nom_fichier_traces, std::ofstream::ate | std::ofstream::out | std::ofstream::app);
	fichier_traces<<trace;
	fichier_traces<<_T("\n");
}

void CTrace::stringCopyAvecEspaces(std::wstring &dest, const std::wstring &source, const short maxLen)
{
	short sourceLen = source.length();

	if(maxLen > sourceLen)
		{
			wchar_t *destTemp = new wchar_t[maxLen+1];
			for(short i=0;i<sourceLen;i++)
			{
				destTemp[i]=source[i];
			}
			for(short i=sourceLen;i<maxLen;i++)
			{
				destTemp[i]=' ';
			}
			destTemp[maxLen]=0;
			dest = destTemp;
			delete [] destTemp;
		}
	else
	{
		dest = source.substr(0, maxLen);
	}
}


The calls look like TRACE(CTrace::NIV_DEB, _T("test"));

The error is :

Melodizer.cpp(36): error C2664: 'std::basic_string<_Elem,_Traits,_Alloc>::basic_string(const std::basic_string<_Elem,_Traits,_Alloc> &)' : impossible to convert parameter 1 from 'const char [14]' into 'const std::basic_string<_Elem,_Traits,_Alloc> &'
          with
          [
              _Elem=wchar_t,
              _Traits=std::char_traits<wchar_t>,
              _Alloc=std::allocator<wchar_t>
          ]
          Reason : impossible to convert from 'const char [14]' into 'const std::basic_string<_Elem,_Traits,_Alloc>'
          with
          [
              _Elem=wchar_t,
              _Traits=std::char_traits<wchar_t>,
              _Alloc=std::allocator<wchar_t>
          ]
          No constructor could take that kind of source, or the override resolution was ambiguous


I wonder if all this would not be much more easier in Java ?
closed account (DSLq5Di1)
Trace.h

:::: Line 18 #include <strstream>

There is a couple of std::strstream objects in your code, but they have long been deprecated in favour of std::stringstream, the appropriate header to include is <sstream>.

:::: Line 24 & 25
http://msdn.microsoft.com/en-us/library/b0084kay(v=vs.80)

:::: Line 26
Replace std::ostrstream with std::wstringstream.

:::: Line 27 os << MESSAGE << std::ends; \

std::ends is an artifact of using std::strstream, remove all occurrences.

:::: Line 29 os.freeze(0); \
Remove freeze(), same reason as above.


Trace.cpp

:::: Line 146 wss << counter << std::ends;
Remove std::ends.

:::: Line 204 & 205 (Forum: Line 49 & 50 in the latter half of trace.cpp)
1
2
char tmpbuf[14];
strftime( tmpbuf, 128, "%y%m%d %H%M%S\0", today);
Buffer overrun, possibly the reason for your garbled output? replace the 128 in strftime() with _countof(tmpbuf).

:::: Line 235 (Forum: Line 80 in the latter half of trace.cpp)
Remove std::ends.


That'll solve the compile and run-time errors I've come across. A couple of suggestions to further clean up your code:-

:::: Lines 29 - 37 (assume I'm referring to the latter half of trace.cpp on the forums from now on)
Hex output using a stream,
1
2
3
4
std::wostringstream strm;
strm << L"0x" << setfill(L'0') << setw(4) << std::hex << GetCurrentThreadId();

entete += strm.str();

:::: Lines 49 - 59
There is typically a wide function for every winapi / c standard function you come across,
1
2
wchar_t tmpbuf[14];
wcsftime(tmpbuf, _countof(tmpbuf), L"%y%m%d %H%M%S", today);


:::: Lines 110 - 133 stringCopyAvecEspaces() could be written as,

1
2
3
4
5
6
void CTrace::stringCopyAvecEspaces(std::wstring &dest, const std::wstring &source, const short maxLen)
{
                dest.assign(maxLen, L' '); // resize dest to maxLen and assign space to each character
		size_t len = source.length() > maxLen ? maxLen : source.length(); // truncate if source > maxLen
		dest.replace(0, len, source.c_str(), len); // replace characters of dest with source
}

Note: I think you misunderstood my earlier post regarding mixing TCHAR with wide functions, don't use TCHARs, TEXT() or _T() now since you are focused on Unicode. wchar_t literals are prefixed with an L. wchar_t example[] = L"hello";.

If you can, avoid using C stdio functions (eg. printf() and it's derivatives), prefer C++ streams, iostream/fstream/stringstream.
Last edited on
Topic archived. No new replies allowed.