delphi 7 richedit and romanian language - delphi

I'm trying to write some Romanian text into a RichEdit component (Delphi 7) , and even i set the Font Property - Charset to "EASTEUROPE_CHARSET" it doesn't work.
What i want to accomplish is to paste some text (in romanian) in a RichEdit, load into a StringList, set the property order to true and assign it to another RichEdit component (sort the list in a alphabetical order).
I know this shouldn't be a problem in Delphi2009 and up, but at this point I can work only with Delphi 7.
word examples : opoziţie, computerizată.
Any ideas?
Best regards,

Try this code, it reads the text from RichEdit1 as UNICODE text, manually converts S and T + Comma to S and T + Cedilla and then uses WideCharToMultiByte to convert the text to code page 1250. The code point conversions need to be done because code page 1250 only encodes the cedilla-based versions of Ş and Ţ, while the new Romanian keyboards under Vista and Windows 7 generate the (correct) comma-based versions of Ş and Ţ!
procedure TForm1.Button1Click(Sender: TObject);
var GetTextStruct:GETTEXTEX;
GetLenStruct:GETTEXTLENGTHEX;
RequiredBytes:Integer;
NumberOfWideChars:Integer;
WideBuff:PWideChar;
AnsiBuff:PChar;
i:Integer;
begin
;
// Get length of text
GetLenStruct.flags := GTL_NUMBYTES or GTL_USECRLF or GTL_PRECISE;
GetLenStruct.codepage := 1200; // request unicode
RequiredBytes := SendMessage(RichEdit1.Handle, EM_GETTEXTLENGTHEX, Integer(#GetLenStruct), 0);
// Prepare structure to get all text
FillMemory(#GetTextStruct, SizeOf(GetTextStruct), 0);
GetTextStruct.cb := SizeOf(GetTextStruct);
GetTextStruct.flags := GT_USECRLF;
GetTextStruct.codepage := 1200; // request unicode
WideBuff := GetMemory(RequiredBytes);
try
// Do the actual request
SendMessage(RichEdit1.Handle, EM_GETTEXTEX, Integer(#GetTextStruct), Integer(WideBuff));
// Replace the "new" diactrics with the old (make Romanian text compatible with code page 1250)
NumberOfWideChars := RequiredBytes div 2;
for i:=0 to NumberOfWideChars-1 do
case Ord(WideBuff[i]) of
$0218: WideBuff[i] := WideChar($015E);
$0219: WideBuff[i] := WideChar($015F);
$021A: WideBuff[i] := WideChar($0162);
$021B: WideBuff[i] := WideChar($0163);
end;
// Convert to code-page 1250
RequiredBytes := WideCharToMultiByte(1250, 0, WideBuff, -1, nil, 0, nil, nil);
AnsiBuff := GetMemory(RequiredBytes);
try
WideCharToMultiByte(1250, 0, WideBuff, -1, AnsiBuff, RequiredBytes, nil, nil);
Memo1.Lines.Text := AnsiBuff; // AnsiBuff now contains the CRLF-terminated version of the
// text in RichEdi1, corectly translated to code page 1250
finally FreeMemory(AnsiBuff);
end;
finally FreeMemory(WideBuff);
end;
end;
Then use something similar to turn AnsiString into UNICODE and push into the RichEdit.
Of course, the only real solution is to switch to Delphi 2009 or Delphi 2010 and use Unicode all over.

i've resolved it with JvWideEditor from Jedi. Code is bellow
procedure TForm2.SortUnicode;
var asrt:TWStringList;
i:Integer;
begin
JvWideEditor1.Lines.Clear;
JvWideEditor2.Lines.Clear;
asrt:=TWStringList.Create;
if OpenDialog1.Execute then
begin
wPath:=OpenDialog1.FileName;
JvWideEditor1.Lines.LoadFromFile(wPath,[foUnicodeLB]);
try
asrt.AddStrings(JvWideEditor1.Lines);
for i:=asrt.Count-1 downto 0 do
begin
if Trim(asrt.Strings[i])='' then
asrt.Delete(i);
end;
asrt.Duplicates:=dupAccept;
asrt.CaseSensitive:=true;
asrt.Sorted:=True;
JvWideEditor2.Lines.AddStrings(asrt);
JvWideEditor2.Lines.SaveToFile(GetCurrentDir+'\res.txt',[foUnicodeLB]);
finally
FreeAndNil(asrt);
end;
end;
end;

Check the language settings in Windows. If you are running English windows, try setting the "treat non-unicode programs as..." to Romanian. Or, run on native Romanian Windows. To run in a mixed environment (needing to show different charsets simultaneously), you'll likely need Unicode.

Related

Write Unicode (UTF-8) text file

How can I write a Unicode text file in Delphi?
Currently I simply use AssignFile, RewriteFile, and Writeln, but this does not write Unicode characters.
You shouldn't be using old Pascal I/O at all. That did its job back in the 80s but is very obsolete today.
This century, you can use the TStringList. This is very commonly used in Delphi. For instance, VCL controls use TStrings to access a memo's lines of text and a combo box's or list box's items.
var SL := TStringList.Create;
try
SL.Add('∫cos(x)dx = sin(x) + C');
SL.Add('¬(a ∧ b) ⇔ ¬a ∨ ¬b');
SL.SaveToFile(FileName, TEncoding.UTF8);
finally
SL.Free;
end;
Fore more advanced needs, you can use a TStreamWriter:
var SW := TStreamWriter.Create(FileName, False, TEncoding.UTF8);
try
SW.WriteLine('αβγδε');
SW.WriteLine('ωφψξη');
finally
SW.Free;
end;
And for very simple needs, there are the new TFile methods in IOUtils.pas:
var S := '⌬ is aromatic.';
TFile.WriteAllText(FileName, S, TEncoding.UTF8); // string (possibly with linebreaks)
var Lines: TArray<string>;
Lines := ['☃ is cold.', '☼ is hot.'];
TFile.WriteAllLines(FileName, Lines, TEncoding.UTF8); // string array
As you can see, all these modern options allow you to specify UTF8 as encoding. If you prefer to use some other encoding, like UTF16, that's fine too.
Just forget about AssignFile, Reset, Rewrite, Append, CloseFile etc.
Other users have given you options but nobody has answered (I guess). You can´t write UTF8 using Writeln because at runtime, any string is switched back to Ansi. All the proposal seems very good ones however.
Try this short program
program utf8;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
var s : string; u : AnsiString; some : Text;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
Assign(some,'data.txt');
rewrite(some);
s := 'física';
u := UTF8Encode (s);
writeln(some,s);
writeln(some,u);
Close(some);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Enable "use debug dcu" and carefully follow the Writeln execution. You will learn that despite the fact of being UTF8 encoded, u is switched back to Ansi at some point.
Edition:
I was wrong. You can indeed with:
Assign(FileName,CP_UTF8);
Check help for System.Assign

TStringlist not loading Google Contacts file

I'm trying to use a Stringlist to load a CSV file generated by Google Contacts. When i open this file in an text editor like Sublime Text, i can see the contents properly, with 75 lines. This is a sample from the Google Contacts file :
Name,Given Name,Additional Name,Family Name,Yomi Name,Given Name Yomi,Additional Name Yomi,Family Name Yomi,Name Prefix,Name Suffix,Initials,Nickname,Short Name,Maiden Name,Birthday,Gender,Location,Billing Information,Directory Server,Mileage,Occupation,Hobby,Sensitivity,Priority,Subject,Notes,Group Membership,Phone 1 - Type,Phone 1 - Value,Phone 2 - Type,Phone 2 - Value,Phone 3 - Type,Phone 3 - Value
H,H,,,,,,,,,,,,, 1-01-01,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,,,,,,
H - ?,H,-,?,,,,,,,,,,, 1-01-01,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,Mobile,031-863-64393,,,,
H - ?,H,-,?,,,,,,,,,,,,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,Mobile,031-986-364393,,,,
BUT when i try to load this same file using Stringlist, this is what i see in the Stringlist.text property :
'ÿþN'#$D#$A
Here is my code :
procedure Tform1.loadfile;
var sl : tstringlist;
begin
sl := tstringlist.create;
sl.loadfromfile('c:\google.csv');
showmessage('lines : '+inttostr(sl.count)+' / text : '+ sl.text);
end;
This is the result i get :
'1 / 'ÿþN'#$D#$A'
What is happening here ?
Thanks
According to the hex dump you provided, the BOM indicates that your file is encoded using UTF-16LE. You a few options in front of you, as I see it:
Switch to Unicode and use the TnT Unicode controls to work with this file.
Read the file as an array of bytes. Convert to ANSI and then continue using ANSI encoded text. Obviously you'll lose information for any characters than cannot be encoded by your ANSI code page. A cheap way to do this would be to read the file as a byte array. Copy the content after the first two bytes, the BOM, into a WideString. Then assign that WideString to an ANSI string.
Port your program to a Unicode version of Delphi (anything later than Delphi 2007) and work natively with Unicode.
I rather suspect that you are not very familiar with text encodings. If you were then I think you would have been able to answer the question yourself. That's just fine but I urge you to take the time to learn about this issue properly. If you rush into coding now, before having a sound grounding, you are sure to make a mess of it. And we've seen so many people make that same mistake. Please don't add to the list of text encoding casualties.
Thanks to the information of David, i could achieve the task by using the function below ; because Delphi 2007 does not have unicode support, it needs third-party function to do it.
procedure loadUnicodeFile( const filename: String; strings: TStringList);
Procedure SwapWideChars( p: PWideChar );
Begin
While p^ <> #0000 Do Begin
// p^ := Swap( p^ ); //<<< D3
p^ := WideChar( Swap( Word(p^)));
Inc( p );
End; { While }
End; { SwapWideChars }
Var
ms: TMemoryStream;
wc: WideChar;
pWc: PWideChar;
Begin
ms:= TMemoryStream.Create;
try
ms.LoadFromFile( filename );
ms.Seek( 0, soFromend );
wc := #0000;
ms.Write( wc, sizeof(wc));
pWC := ms.Memory;
If pWc^ = #$FEFF Then // normal byte order mark
Inc(pWc)
Else If pWc^ = #$FFFE Then Begin // byte order is big-endian
SwapWideChars( pWc );
Inc( pWc );
End { If }
Else; // no byte order mark
strings.Text := WideChartoString( pWc );
finally
ms.free;
end;
End;

Delphi - Store WideStrings inside a program

In the past I used INI-Files to store unicode text, but now I need to store unicode text in the executable. How can I achieve this?
I want to store these letters:
āčēūīšķļņž
If you want to save the Unicode INI files then you might try the following code. The files are saved in UTF8 encoding.
Also you might take a look at this Unicode library where you can find a lot of helper functions.
uses IniFiles;
function WideStringToUTF8(const Value: WideString): AnsiString;
var
BufferLen: Integer;
begin
Result := '';
if Value <> '' then
begin
BufferLen := WideCharToMultiByte(CP_UTF8, 0, PWideChar(Value), -1, nil, 0, nil, nil);
SetLength(Result, BufferLen - 1);
if BufferLen > 1 then
WideCharToMultiByte(CP_UTF8, 0, PWideChar(Value), -1, PAnsiChar(Result), BufferLen - 1, nil, nil);
end;
end;
function UTF8ToWideString(const Value: AnsiString): WideString;
var
BufferLen: integer;
begin
Result := '';
if Value <> '' then
begin
BufferLen := MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(Value), -1, nil, 0);
SetLength(Result, BufferLen - 1);
if BufferLen > 1 then
MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(Value), -1, PWideChar(Result), BufferLen - 1);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
IniFile: TIniFile;
const
UnicodeValue = WideString(#$0101#$010D#$0113#$016B#$012B#$0161);
begin
IniFile := TIniFile.Create('C:\test.ini');
try
IniFile.WriteString('Section', 'Key', WideStringToUTF8(UnicodeValue));
IniFile.UpdateFile;
finally
IniFile.Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
IniFile: TIniFile;
UnicodeValue: WideString;
begin
IniFile := TIniFile.Create('C:\test.ini');
try
UnicodeValue := UTF8ToWideString(IniFile.ReadString('Section', 'Key', 'Default'));
MessageBoxW(Handle, PWideChar(UnicodeValue), 'Caption', 0);
finally
IniFile.Free;
end;
end;
with Delphi 2007 on 64-bit Windows 7 Enterprise SP 1
If you definitely need to use Delphi 7 there are some variants:
Store strings in resources linked to executable file.
Store strings in big memo or same thing, located on global data module or any other visual or non-visual component and access it by index. It's possible because strings in Delphi resources stored in XML-encoded form. E.g. your symbols example āčēūīšķļņž will be stored as āčēūīšķļņž
Store XML-encoded or Base64-encoded strings in string constants inside your code.
For string conversion you can use EncdDecd.pas , xdom.pas or some functions of System.pas like UTF8Encode/UTF8Decode.
To display and edit Unicode strings in Delphi forms you can use special set of Unicode controls like TNT Unicode Controls or subclass original Delphi controls and do some other workarounds by yourself, like described in this excerpt from comments in TntControls.pas (part of TNT Unicode Controls):
Windows NT provides support for native Unicode windows. To add
Unicode support to a
TWinControl descendant, override CreateWindowHandle() and call
CreateUnicodeHandle().
One major reason this works is because the VCL only uses the ANSI
version of
SendMessage() -- SendMessageA(). If you call SendMessageA() on a
UNICODE
window, Windows deals with the ANSI/UNICODE conversion
automatically. So
for example, if the VCL sends WM_SETTEXT to a window using
SendMessageA,
Windows actually expects a PAnsiChar even if the target window
is a UNICODE
window. So caling SendMessageA with PChars causes no problems.
A problem in the VCL has to do with the TControl.Perform() method.
Perform()
calls the window procedure directly and assumes an ANSI window.
This is a
problem if, for example, the VCL calls Perform(WM_SETTEXT, ...)
passing in a
PAnsiChar which eventually gets passed downto DefWindowProcW()
which expects a PWideChar.
This is the reason for SubClassUnicodeControl(). This procedure
will subclass the
Windows WndProc, and the TWinControl.WindowProc pointer. It will
determine if the
message came from Windows or if the WindowProc was called
directly. It will then
call SendMessageA() for Windows to perform proper conversion on
certain text messages.
Another problem has to do with TWinControl.DoKeyPress(). It is
called from the WM_CHAR
message. It casts the WideChar to an AnsiChar, and sends the
resulting character to
DefWindowProc. In order to avoid this, the DefWindowProc is
subclassed as well. WindowProc
will make a WM_CHAR message safe for ANSI handling code by
converting the char code to
#FF before passing it on. It stores the original WideChar in the
.Unused field of TWMChar.
The code #FF is converted back to the WideChar before passing onto
DefWindowProc.
Do
const MyString = WideString('Teksts latvie'#$0161'u valod'#$0101);
Simple, the idea is to find a non-visual component, which can store text and store your text there. Prefer that such component can also provide you an editor to edit the text in design time.
There is a component call FormResource which can do this. I use TUniScript. I believe there are other similar components. However, I did not find a usable component from the standard library.
The approach Widestring(#$65E5#$672C) does not work, because Delphi 7 just doesn't expect more than one byte for the #, so the outcome is by far not what you expect when going above 255 or $FF.
Another approach WideChar($65E5)+ WideChar($672C) can be used to store single Unicode codepoints in your source code when knowing that you need to have a Widestring at the start of the assignment (which can also be an empty literal) so the compiler understands which datatype you want:
const
// Compiler error "Imcompatible types"
WONT_COMPILE: WideChar($65E5)+ WideChar($672C);
// 日本
NIPPON: Widestring('')+ WideChar($65E5)+ WideChar($672C);
Looks cumbersome, but surely has your UTF-16 texts in Delphi 7.
Alternatively, store your constants in UTF-8, which is ASCII safe - that way you can use # easily. One advantage is, that it's a lot less cumbersome to write in your source code. One disadvantage is, that you can never use the constant directly, but have to convert it to UTF-16 first:
const
// UTF-8 of the two graphemes 日 and 本, needing 3 bytes each
NIPPON: #$E6#$97#$A5#$E6#$9C#$AC;
var
sUtf16: Widestring;
begin
// Internally these are 2 WORDs: $65E5 and $672C
sUtf16:= UTF8ToWideString( NIPPON );

OLE Automation: How do i copy text between Word documents without using the clipboard

While doing som Word automation from Delphi XE, I have two documents open simultaneously. I want to copy the contents of a given range of one document to another range in the other document. How can I do this?
Consider the following code:
procedure TForm1.ManipulateDocuments;
var
vDoc1,vDoc2 : TWordDocument;
vFilename : olevariant;
vRange1,vRange2 : Range;
begin
vDoc1 := TWordDocument.Create(nil);
vDoc2 := TWordDocument.Create(nil);
try
vFilename := 'c:\temp\test1.doc';
vDoc1.ConnectTo(FWordApp.Documents.Open(vFilename,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam));
vFilename := 'c:\temp\test2.doc';
vDoc2.ConnectTo(FWordApp.Documents.Open(vFilename,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam));
vRange1 := GetSourceRange(vDoc1);
vRange2 := GetDestinationRange(vDoc2);
vRange2.CONTENTS := vRange1.CONTENTS; //What should I substitute for CONTENTS?
finally
vDoc1.Free;
vDoc2.Free;
end;
end;
Is there something I could substitute for CONTENTS? I can't use text, since I want to copy formatting, bookmarks, field codes etc. Do I have to do it another way alltogether? Any suggestions?
I don't know a way for earlier versions of Word, but for newer versions (2007 and up) you can export a range from a document to a fragment file, and then import it from another document. If you want early binding, you might need to import the type library (msword.olb), I don't know if Delphi XE has it. Otherwise the code might look like this:
function GetTempFileName(Prefix: string): string;
begin
SetLength(Result, MAX_PATH);
GetTempPath(MAX_PATH, PChar(Result));
windows.GetTempFileName(PChar(Result), PChar(Prefix), 0, PChar(Result));
end;
procedure TForm2.Button1Click(Sender: TObject);
const
// wdFormatDocument = 0;
wdFormatRTF = $00000006;
var
WordApp : OleVariant;
fragment: string;
vDoc1, vDoc2: OleVariant;
vRange1, vRange2: OleVariant;
begin
try
WordApp := GetActiveOleObject('Word.Application');
except
WordApp := CreateOleObject('Word.Application');
end;
WordApp.Visible := True;
vDoc1 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test1.doc');
vRange1 := vDoc1.Range(20, 120); // the export range
fragment := GetTempFileName('frg');
vRange1.ExportFragment(fragment, wdFormatRTF);
try
vDoc2 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test2.doc');
vRange2 := vDoc2.Range(15, 15); // where to import
vRange2.ImportFragment(fragment);
finally
DeleteFile(fragment);
end;
end;
With my test, 'document' format threw an error (something like not being able to insert XML formatting), hence usage of RTF format.
edit:
With earlier versions, it seems to be possible to insert a named selection from one document to a selection in another document. The result seems not to be perfect regarding formatting if one of the selections happens to be in the middle of some text. But otherwise it seems to be working good.
...
WordApp.Visible := True;
vDoc1 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test1.doc');
vRange1 := vDoc1.Range(20, 188); // the transfer range
vDoc1.Bookmarks.Add('TransferSection', vRange1); // arbitrary bookmark name
vDoc2 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test2.doc');
vRange2 := vDoc2.Range(103, 104); // where to import the bookmark
vRange2.Select;
vDoc2.ActiveWindow.Selection.InsertFile(vDoc1.FullName, 'TransferSection');
vDoc1.Bookmarks.Item('TransferSection').Delete; // no need for the bookmark anymore
 
If you can use the Office Open XML-format (ie. the docx file format that was introduced in Word 2007), then you can do this without automation.
Word versions prior to 2007 must install a compatibility pack which will enable docx-files for Word 2003, 2002 and 2000.
The docx-file is actually a zip-file that contains several xml-files. Try to change the extension of a docx-file from .docx to .zip and open this file in eg. WinZip.
So... Unzip docx-file and grab the xml-part you need. As pure string or as a xml document. Then you can inject this xml-part into the other docx-file. You need to know where in the xml-structure to grab/insert the xml, though. This will depend on how well you know the document structure and how much editing the user is allowed to do in the document.
I don't know how Word will handle duplicate bookmark names etc with this approach.
It seems I found the canonical solution to this question while digged into similar problem. The FormattedText property of Range object is the exact what do you need. Just use:
vRange2.FormattedText := vRange1;
and the contents of vRange1 will be copied into vRange2. Also, this works too:
vRange2 := vRange1;
Though, the second statement doesn't copy the formatting.
Why not use the clipboard? If all the text is selected in vDoc1, then to copy this to the clipboard involves one simple call: vDoc1.copy. Similarly, copying the contents of the clipboard to the second document requires one simple call: vDoc2.paste. The clipboard buffer will hold all the formatting information.

How can I get this File Writing code to work with Unicode (Delphi)

I had some code before I moved to Unicode and Delphi 2009 that appended some text to a log file a line at a time:
procedure AppendToLogFile(S: string);
// this function adds our log line to our shared log file
// Doing it this way allows Wordpad to open it at the same time.
var F, C1 : dword;
begin
if LogFileName <> '' then begin
F := CreateFileA(Pchar(LogFileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0);
if F <> 0 then begin
SetFilePointer(F, 0, nil, FILE_END);
S := S + #13#10;
WriteFile(F, Pchar(S)^, Length(S), C1, nil);
CloseHandle(F);
end;
end;
end;
But CreateFileA and WriteFile are binary file handlers and are not appropriate for Unicode.
I need to get something to do the equivalent under Delphi 2009 and be able to handle Unicode.
The reason why I'm opening and writing and then closing the file for each line is simply so that other programs (such as WordPad) can open the file and read it while the log is being written.
I have been experimenting with TFileStream and TextWriter but there is very little documentation on them and few examples.
Specifically, I'm not sure if they're appropriate for this constant opening and closing of the file. Also I'm not sure if they can make the file available for reading while they have it opened for writing.
Does anyone know of a how I can do this in Delphi 2009 or later?
Conclusion:
Ryan's answer was the simplest and the one that led me to my solution. With his solution, you also have to write the BOM and convert the string to UTF8 (as in my comment to his answer) and then that worked just fine.
But then I went one step further and investigated TStreamWriter. That is the equivalent of the .NET function of the same name. It understands Unicode and provides very clean code.
My final code is:
procedure AppendToLogFile(S: string);
// this function adds our log line to our shared log file
// Doing it this way allows Wordpad to open it at the same time.
var F: TStreamWriter;
begin
if LogFileName <> '' then begin
F := TStreamWriter.Create(LogFileName, true, TEncoding.UTF8);
try
F.WriteLine(S);
finally
F.Free;
end;
end;
Finally, the other aspect I discovered is if you are appending a lot of lines (e.g. 1000 or more), then the appending to the file takes longer and longer and it becomes quite inefficient.
So I ended up not recreating and freeing the LogFile each time. Instead I keep it open and then it is very fast. The only thing I can't seem to do is allow viewing of the file with notepad while it is being created.
For logging purposes why use Streams at all?
Why not use TextFiles? Here is a very simple example of one of my logging routines.
procedure LogToFile(Data:string);
var
wLogFile: TextFile;
begin
AssignFile(wLogFile, 'C:\MyTextFile.Log');
{$I-}
if FileExists('C:\MyTextFile.Log') then
Append(wLogFile)
else
ReWrite(wLogFile);
WriteLn(wLogfile, S);
CloseFile(wLogFile);
{$I+}
IOResult; //Used to clear any possible remaining I/O errors
end;
I actually have a fairly extensive logging unit that uses critical sections for thread safety, can optionally be used for internal logging via the OutputDebugString command as well as logging specified sections of code through the use of sectional identifiers.
If anyone is interested I'll gladly share the code unit here.
Char and string are Wide since D2009. Thus you should use CreateFile instead of CreateFileA!
If you werite the string you shoudl use Length( s ) * sizeof( Char ) as the byte length and not only Length( s ). because of the widechar issue. If you want to write ansi chars, you should define s as AnsiString or UTF8String and use sizeof( AnsiChar ) as a multiplier.
Why are you using the Windows API function instead of TFileStream defined in classes.pas?
Try this little function I whipped up just for you.
procedure AppendToLog(filename,line:String);
var
fs:TFileStream;
ansiline:AnsiString;
amode:Integer;
begin
if not FileExists(filename) then
amode := fmCreate
else
amode := fmOpenReadWrite;
fs := TFileStream.Create(filename,{mode}amode);
try
if (amode<>fmCreate) then
fs.Seek(fs.Size,0); {go to the end, append}
ansiline := AnsiString(line)+AnsiChar(#13)+AnsiChar(#10);
fs.WriteBuffer(PAnsiChar(ansiline)^,Length(ansiline));
finally
fs.Free;
end;
Also, try this UTF8 version:
procedure AppendToLogUTF8(filename, line: UnicodeString);
var
fs: TFileStream;
preamble:TBytes;
outpututf8: RawByteString;
amode: Integer;
begin
if not FileExists(filename) then
amode := fmCreate
else
amode := fmOpenReadWrite;
fs := TFileStream.Create(filename, { mode } amode, fmShareDenyWrite);
{ sharing mode allows read during our writes }
try
{internal Char (UTF16) codepoint, to UTF8 encoding conversion:}
outpututf8 := Utf8Encode(line); // this converts UnicodeString to WideString, sadly.
if (amode = fmCreate) then
begin
preamble := TEncoding.UTF8.GetPreamble;
fs.WriteBuffer( PAnsiChar(preamble)^, Length(preamble));
end
else
begin
fs.Seek(fs.Size, 0); { go to the end, append }
end;
outpututf8 := outpututf8 + AnsiChar(#13) + AnsiChar(#10);
fs.WriteBuffer(PAnsiChar(outpututf8)^, Length(outpututf8));
finally
fs.Free;
end;
end;
If you try to use text file or Object Pascal typed/untyped files in a multithreaded application you gonna have a bad time.
No kidding - the (Object) Pascal standard file I/O uses global variables to set file mode and sharing. If your application runs in more than one thread (or fiber if anyone still use them) using standard file operations could result in access violations and unpredictable behavior.
Since one of the main purposes of logging is debugging a multithreaded application, consider using other means of file I/O: Streams and Windows API.
(And yes, I know it is not really an answer to the original question, but I do not wish to log in - therefor I do not have the reputation score to comment on Ryan J. Mills's practically wrong answer.)

Resources