I encountered the situation where file names might contain "extended" unicode characters (ie. not like a degrees symbol:
° but multi-byte characters/symbols, eg Alt+NumPlus+Num3
♥). Once I got around the problems of using FindFirstUtf8 instead of FindFirst I couldn't open these files using a TFileStream. Using Utf8ToSys replaces the extended unicode characters with question marks (it did convert the degrees symbol I think).
The problem is of course that the API used by TFileStream ends up being
CreateFileA. One solution I found whilst searching was to use the cAlternateFileName returned in the search record, but this I feel is just a workaround (and probably the alternate name would need to be found for each directory in case they too contained extended unicode characters) so I ended up writing my own class (called TFileStreamUTF8 - based on TFileStream but derived from THandleStream). I've provided it below in case anybody else wants it - it could really be added to Lazarus/FPC too (
classesh.inc &
streams.inc) - maybe if I knew how I would one day.
However during writing this I had some issues with
TFileStream as currently implemented (I have Lazarus 0.9.30.4 with FPC 2.6.0 SVN:35940):
- TFileStream declares two overloaded constructors. Both implement full code but the one without the Rights parameters is exactly the same except it uses a "magic" 438 when calling FileCreate - why doesn't this constructor just call the other constructor with this number as the parameter? [That will probably create another instance but rather than duplicating code the body could be extracted into a single private/protected method to avoid duplication like I've done for TFileStreamUTF8.]
- These constructors don't call an inherited constructor. Its likely they don't need to as none of the ancestors appear to dynamically create data but I think inherited should be called. The only field contained in the ancestor is the stream Handle and I assume the default object constructor will create space for this member variables.
- Most/all TStream descendents have private member fields (eg. THandleStream has a private FHandle). This means descendents can't access these members properly (if they aren't exposed with read and write property methods). The way I got around it was by calling the inherited constructor but I could have done it differently if I had write access to FHandle.
unit uFileStreamUtf8;
{
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
}
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
type
TFileStreamUTF8 = class(THandleStream)
private
protected
FFileName : String;
procedure DoCreate(const AFileName: string; Mode: Word; Rights: Cardinal);
public
constructor Create(const AFileName: string; Mode: Word);
constructor Create(const AFileName: string; Mode: Word; Rights: Cardinal);
destructor Destroy; override;
property FileName : String Read FFilename;
property Handle;
end;
function FileCreateUtf8 (Const AFileName : String; ShareMode : Integer; Rights : Integer) : THandle;
implementation
uses
Windows, rtlconsts;
const { redeclared because they are F#$%ing private in SysUtils }
AccessMode: array[0..2] of Cardinal = (
GENERIC_READ,
GENERIC_WRITE,
GENERIC_READ or GENERIC_WRITE);
ShareModes: array[0..4] of Integer = (
0,
0,
FILE_SHARE_READ,
FILE_SHARE_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE);
function FileCreateUtf8(Const AFileName : String; ShareMode : Integer; Rights : Integer) : THandle;
begin
Result := CreateFileW(PWideChar(AFileName), GENERIC_READ or GENERIC_WRITE,
dword(ShareModes[(ShareMode and $F0) shr 4]), nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
end;
function FileOpenUtf8(Const AFileName : string; Mode : Integer) : THandle;
var
fname : UnicodeString;
begin
fname := UTF8Decode(AFileName);
result := Windows.CreateFileW(PWideChar(fname), dword(AccessMode[Mode and 3]),
dword(ShareModes[(Mode and $F0) shr 4]), nil, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0);
//if fail api return feInvalidHandle (INVALIDE_HANDLE=feInvalidHandle=-1)
end;
constructor TFileStreamUTF8.Create(const AFileName: string; Mode: Word);
begin
DoCreate(AFileName, Mode, 438); // wtf is 438?
end;
constructor TFileStreamUTF8.Create(const AFileName: string; Mode: Word; Rights: Cardinal);
begin
DoCreate(AFileName, Mode, Rights);
end;
procedure TFileStreamUTF8.DoCreate(const AFileName: string; Mode: Word; Rights: Cardinal);
var
h : THandle;
begin
If (Mode and fmCreate) > 0 then
h := FileCreateUtf8(AFileName, Mode, Rights)
else
h := FileOpenUtf8(AFileName, Mode);
If (THandle(h) = feInvalidHandle) then
If Mode=fmcreate then
raise EFCreateError.createfmt(SFCreateError,[AFileName])
else
raise EFOpenError.Createfmt(SFOpenError,[AFilename]);
inherited Create(h);
FFileName := AFileName;
if handle = 0 then
h:=1;
end;
destructor TFileStreamUTF8.Destroy;
begin
FileClose(Handle);
end;
end.