Textdatei mit Unicodezeichen erstellen schlägt fehl



  • Moin zusammen!

    Ich habe ein kleines Problem mit Unicode und Textdateien. Mit ersterem habe ich noch nie gearbeitet, soll nun aber dafür eine Log-Funktion schreiben.
    Das hier habe ich zusammengebastelt, allerdings steht manchmal nur Mist in der Datei, andere Male schmiert gleich das ganze Programm mit einer Zugriffsverletzung ab. Was Debugging angeht bin ich auch noch Anfänger, aber das Problem scheint ab einschließlich wsprintf zu liegen.

    #include <stdio.h>
    #include <windows.h>
    
    #define LOG_FILENAME	TEXT("data.log")
    
    BOOL WriteLog( LPWSTR lptsData ) {
    	HANDLE hLogfile;
    	DWORD dwBuffersize;
    	void *pBuffer;
    
    	hLogfile = CreateFile(	LOG_FILENAME,
    				GENERIC_READ | GENERIC_WRITE,
    				FILE_SHARE_WRITE | FILE_SHARE_READ,
    				NULL,
    				OPEN_ALWAYS,
    				FILE_ATTRIBUTE_HIDDEN,
    				NULL );
    	if ( hLogfile == INVALID_HANDLE_VALUE ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	// calculate size for our buffer, including lenghts of input and format string
    	dwBuffersize = wcslen( lptsData ) + wcslen( L"U: \n" ) + 1;
    
    	if ( dwBuffersize <= 0 ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	pBuffer = malloc( dwBuffersize );
    	if ( pBuffer == NULL ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	wsprintf( (char*)pBuffer, "U: %ls\n", lptsData );
    
    	// calculate EOF
    	OVERLAPPED ovlp;
    	ovlp.OffsetHigh = ovlp.Offset = 0xFFFFFFFF;
    	ovlp.hEvent = NULL;	
    
    	WriteFile( hLogfile, pBuffer, dwBuffersize, NULL, &ovlp );
    
    	CloseHandle( hLogfile );
    	free( pBuffer );
    
    	return TRUE;	
    }
    

    Hierzu sei gesagt, dass das ganze auch als Nicht-Unicode gespeichert werden könnte, nur weiß ich nicht wie ich das dann vor dem Speichern konvertieren muss.
    Das MSDN habe ich auch schon durchwühlt, aber wirklich schlauer bin ich davon nicht geworden 😕
    Wäre es sinnvoll, mit einem Buffer fester Größe zu arbeiten? Die Logeinträge sollen ja sowieso nicht ewig lang werden 😉



  • Wenn du LPWSTR (in diesem Fall wäre passender: LPCWSTR) und wcslen verwendest solltest du auch swprintf verwenden (nicht wsprintf!).
    Davon abgesehen liefert wcslen die Anzahl an wchar_t zurück, nicht die Anzahl an Bytes (ein wchar_t ist 2 Byte gross). malloc und WriteFile erwarten aber die Anzahl der Bytes.



  • Schonmal danke dafür, ich habe den Quellcode jetzt so umgeschrieben:

    #include <stdio.h>
    #include <windows.h>
    
    #define LOG_FILENAME	TEXT("event.log")
    
    BOOL WriteLog( LPCWSTR lptsUser, LPCWSTR lptsEvent ) {
    	HANDLE hLogfile;
    	DWORD dwCharcount, dwBuffersize;
    	void *pBuffer;
    
    	hLogfile = CreateFile(	LOG_FILENAME,
    				GENERIC_READ | GENERIC_WRITE,
    				FILE_SHARE_WRITE | FILE_SHARE_READ,
    				NULL,
    				OPEN_ALWAYS,
    				FILE_ATTRIBUTE_HIDDEN,
    				NULL );
    	if ( hLogfile == INVALID_HANDLE_VALUE ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	// calculate size for our buffer, including lenghts of input and format string and \0
    	dwCharcount = wcslen( lptsUser ) + wcslen( lptsEvent ) + wcslen( L"U:  E: \n" ) + 1;
    	dwBuffersize = dwCharcount * sizeof(wchar_t);
    
    	if ( dwBuffersize <= 0 ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	pBuffer = malloc( dwBuffersize );
    	if ( pBuffer == NULL ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	swprintf( (wchar_t*)pBuffer, L"U: %s E: %s\n", lptsUser, lptsEvent );
    
    	// calculate EOF
    	OVERLAPPED ovlp;
    	ovlp.OffsetHigh = ovlp.Offset = 0xFFFFFFFF;
    	ovlp.hEvent = NULL;
    
    	WriteFile( hLogfile, pBuffer, dwBuffersize, NULL, &ovlp );
    
    	CloseHandle( hLogfile );
    	free( pBuffer );
    
    	return TRUE;
    }
    

    Das klappt nun auch soweit, aber nur Notepad zeigt die Datei einigermaßen korrekt an, andere Editoren können das Format nicht feststellen, da ich das ja nirgendwo schreibe.
    Nun also die zweite Frage, wie schreibe ich die Kodierung mit in die Datei und welche ist das überhaupt?



  • Scheinbar werden noch "überflüssige" Zeichen mit in die Datei geschrieben.
    Soe sieht der Log in Notepad++ aus
    http://img261.imageshack.us/img261/4641/notepadnw8.jpg
    und so im normalen Notepad
    http://img261.imageshack.us/img261/1489/notepad2np2.jpg



  • Hast du denn die BOM reingeschrieben?

    Und deinen Code verstehe ich nicht ganz, was willst du z.B. mit der Overlapped-Struktur, wenn du die Datei nicht-overlapped öffnest? Wenn du einfach an's Ende der Datei schreiben willst, setz den Dateizeiger dahin (mit SetFilePointer und GetFileSize).

    Und FILE_SHARE_WRITE würde ich nicht mit angeben, gibt doch nur Chaos.



  • Genau danach habe ich ja gefragt 😉
    Ich habe jetzt die Textdatei einmal im Editor und einmal mit meinem Programm erstellt und in meiner Version sind noch zusätzliche Zeichen, die die Datei unlesbar machen.
    Hier mal die Datei als Hex-Dump:

    Programm schrieb:

    ff fe 00 00 55 00 3a 00 20 00 74 00 65 00 73 00 74
    00 31 00 20 00 50 00 3a 00 20 00 74 00 65 00 73 00
    74 00 31 00 20 00 4f 00 3a 00 20 00 74 00 65 00 73
    00 74 00 31 00 20 00 44 00 3a 00 20 00 74 00 65 00
    73 00 74 00 31 00 0a 00 00 00

    Notepad schrieb:

    ff fe 55 00 3a 00 20 00 74 00 65 00 73 00 74 00 31
    00 20 00 50 00 3a 00 20 00 74 00 65 00 73 00 74 00
    31 00 20 00 4f 00 3a 00 20 00 74 00 65 00 73 00 74
    00 31 00 20 00 44 00 3a 00 20 00 74 00 65 00 73 00
    74 00 31 00 0d 00 0a 00

    Wie man sieht, fügt mein Programm einmal nach der BOM und ans Ende 00 00 zu viel. Liegt das an den Buffer-Größen? Wenn ich beim beiden WriteFile 2 (also sizeof(wchar_t)) von der eigentlichen Buffergröße abziehe, werden die überflüssigen 00 nicht mehr geschrieben.

    Nach Badestrands Hinweis sieht der Code nun wie folgt aus (und erzeugt noch die 00):

    #include <stdio.h>
    #include <windows.h>
    
    #define LOG_FILENAME	TEXT("event.log")
    
    BOOL WriteLog( LPCWSTR lptsUser, LPCWSTR lptsEvent ) {
    	HANDLE hLogfile;
    	DWORD dwCharcount, dwBuffersize, dwBytesWritten;
    	void *pBuffer;
    
    	hLogfile = CreateFile(	LOG_FILENAME,
    				GENERIC_READ | GENERIC_WRITE,
    				FILE_SHARE_READ,
    				NULL,
    				OPEN_ALWAYS,
    				FILE_ATTRIBUTE_HIDDEN,
    				NULL );
    	if ( hLogfile == INVALID_HANDLE_VALUE ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	// if creating a new log, write BOM at the beginning
    	DWORD dwLasterror = GetLastError();
    	if ( dwLasterror == 0 ) { // file exists
    		wchar_t bom[] = L"\uFEFF"; // UTF-16, big endian
    		WriteFile( hLogfile, &bom, sizeof(bom), &dwBytesWritten, NULL );
    	}
    
    	// calculate size for our buffer, including lenghts of input and format string and \0
    	dwCharcount = wcslen( lptsUser ) + wcslen( lptsEvent ) + wcslen( L"U:  E: \n" ) + 1;
    	dwBuffersize = dwCharcount * sizeof(wchar_t);
    
    	if ( dwBuffersize <= 0 ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	pBuffer = malloc( dwBuffersize );
    	if ( pBuffer == NULL ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	swprintf( (wchar_t*)pBuffer, L"U: %s E: %s \n", lptsUser, lptsEvent );
    
    	// append at end of file
    	SetFilePointer( hLogfile, 0, NULL, FILE_END );
    
    	WriteFile( hLogfile, pBuffer, dwBuffersize, &dwBytesWritten, NULL );
    
    	CloseHandle( hLogfile );
    	free( pBuffer );
    
    	return TRUE;
    }
    


  • Irgendwie so:

    // Statt:
    wchar_t bom[] = L"\uFEFF"; // UTF-16, big endian
    
    // Eher:
    char bom[] = { 0xFE, 0xFF }; // UTF-16, big endian
    

    Ein wchar_t hat keine definierte Größe, hängt afaik vom Compiler ab.

    Übrigens, arbeiten x86er nicht mit Little Endian?


  • Mod

    Warum machst Du es Dir so komplitziert wenn die CRT doch einen perfekten Support inkl. BOM Schreiben bietet.
    http://msdn.microsoft.com/en-us/library/yeby3zcb(VS.80).aspx
    Siehe encoding. Dann winfach die entsprechenden Unicode Texte ausgeben und fertig.

    BTW: Warum verwendest Du überhaupt sprintf? Du gibst doch nur ein paar kostante Texte aus und dazwischen jeweils einen Text., dasbekommst Du auch mit 4 Writes hin... Das umkopieren in einen eigenen Buffer mit sprintf ist absolut unnötig.



  • Das MSDN meint auch Little Endian, aber mit Notepad erstellte Dateien haben Big Endian und mein Log ist so auch lesbar.

    Hier mal der letzte Stand des Codes, der soweit auch funktioniert. Jetzt muss er nur noch in's Programm eingebaut werden..

    #include <stdio.h>
    #include <windows.h>
    
    #define LOG_FILENAME	TEXT("event.log")
    
    BOOL WriteLog( PCWSTR ptsUser, PCWSTR ptsEvent ) {
    	HANDLE hLogfile;
    	DWORD dwCharcount, dwBuffersize, dwBytesWritten;
    	void *pBuffer;
    
    	if ( ( ptsUser == NULL ) || ( ptsEvent == NULL ) {
    		return FALSE;
    	}
    
    	hLogfile = CreateFile(	LOG_FILENAME,
    				GENERIC_WRITE,
    				FILE_SHARE_READ,
    				NULL,
    				OPEN_ALWAYS,
    				FILE_ATTRIBUTE_HIDDEN,
    				NULL );
    	if ( hLogfile == INVALID_HANDLE_VALUE ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	// if creating a new log, write BOM at the beginning
    	DWORD dwLasterror = GetLastError();
    	if ( dwLasterror == 0 ) {		// file exists
    		wchar_t bom[] = L"\uFEFF";	// UTF-16, big endian
    		if ( WriteFile( hLogfile, &bom, sizeof(bom)-sizeof(wchar_t), &dwBytesWritten, NULL ) == FALSE ) {
    			CloseHandle( hLogfile );
    			return FALSE;
    		}
    	}
    
    	// calculate size for our buffer, including lenghts of input and format string and \0
    	dwCharcount = wcslen( ptsUser ) + wcslen( ptsEvent ) + wcslen( L"U:  E: \n" ) + 1;
    	dwBuffersize = dwCharcount * sizeof(wchar_t);
    
    	if ( dwBuffersize <= 0 ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	pBuffer = (void*)LocalAlloc( LPTR, dwBuffersize );
    	if ( pBuffer == NULL ) {
    		CloseHandle( hLogfile );
    		return FALSE;
    	}
    
    	if ( _snwprintf( (wchar_t*)pBuffer, dwCharcount, L"U: %s E: %s\n", ptsUser, ptsEvent ) == -1 ) {
    		CloseHandle( hLogfile );
    		LocalFree( pBuffer );
    		return FALSE;
    	}
    
    	if ( SetFilePointer( hLogfile, 0, NULL, FILE_END ) == INVALID_SET_FILE_POINTER ) {
    		CloseHandle( hLogfile );
    		LocalFree( pBuffer );
    		return FALSE;
    	}
    
    	if ( WriteFile( hLogfile, pBuffer, dwBuffersize-sizeof(wchar_t), &dwBytesWritten, NULL ) == FALSE ) {
    		CloseHandle( hLogfile );
    		LocalFree( pBuffer );
    		return FALSE;
    	}
    
    	CloseHandle( hLogfile );
    	LocalFree( pBuffer );
    
    	return TRUE;
    }
    

    Ja, das ständige return ist nicht sehr schön, aber es funktioniert erstmal :p
    _wfopen_s kannte ich noch nicht, daher der etwas steinige Weg.
    Das Codebeispiel hier ist stark abgespeckt, im richtigen Programm ist der Formatstring schon etwas komplexer, deswegen bleibe ich auch bei der Lösung mit dem Extra-Buffer.
    Ich muss doch nur den Aufruf von CreateFile druch folgendes ersetzen?

    #define LOG_FILENAME	L"event.log"
    
    int iError;
    FILE fLogfile;
    wchar_t wcFilemode[] = L"a+";
    
    iError = _wfopen_s( &fLogfile, LOG_FILENAME, wcFilemode );
    if ( iError == EINVAL ) {
    	return FALSE;
    }
    
    // usw.
    

  • Mod

    Du musst doch fopen kennen!
    Dein Open Befehl sollte auch ein encoding setzen ccs=UNICODE! Lies doch bitte mal den Link:
    http://msdn.microsoft.com/en-us/library/z5hh6ee9(VS.80).aspx



  • Martin Richter schrieb:

    Du musst doch fopen kennen!
    Dein Open Befehl sollte auch ein encoding setzen ccs=UNICODE! Lies doch bitte mal den Link:
    http://msdn.microsoft.com/en-us/library/z5hh6ee9(VS.80).aspx

    Ich glaube zwar mich mit Windows und dem MSVC halbwegs gut auszukenne, aber dass fopen beim MSVC BOMs und Unicode im Allgemeinen versteht/unterstützt war mir auch neu.
    Danke für den Hinweis übrigens 🙂


  • Mod

    Das gibt es schon seit VS-2005
    Und es wurde nicht wenig darüber geblogt und geschrieben.
    http://blog.m-ri.de/index.php/2007/07/03/vc-2005-features-der-crt-fuer-unicode-unterstuetzung/
    Das ist zwar MFC gehört aber auch mit dazu:
    http://blog.kalmbachnet.de/?postid=105
    http://blog.kalmbachnet.de/?postid=107



  • Hi leute
    Mal ne frage:
    trotz heutiger 32bit breite hatte ich mir angewöhnt bei Dateizugriffen meist nur 8bit zu nutzen. Und da ein Standardeditor (HEX, text) auch eigentlich nicht mehr braucht, warum sollte man von ANSI, UTF-8 auf höhere (ISO) standards ausweichen? Wenn man binäre datenblöcke hat, werden diese doch meistens mit einem filestream rein und rausgeschoben ??

    Danke schon mal für eure Antworten :xmas1:


  • Mod

    @zeusosc:
    Kommt immer auf die Anforderungen an.
    Ich bevorzuge bei der Ausgabe von Unicode Daten in Streams UTF-8.


Anmelden zum Antworten