2012-10-15 23 views
0

我有一個測試Delphi應用程序,它使用TFileStream將UTF-8 BOM寫入文本文件,然後是一行文本。爲什麼在更改文件屬性時會丟失我的UTF-8 BOM?

所有按預期工作,並使用Notepad ++的十六進制查看器插件我在輸出文本文件中看到BOM。但是,如果我在重新打開該文件時更改了文本文件的屬性(以編程方式在Delphi中或通過Windows資源管理器),則BOM已被刪除。

的BOM和虛擬數據寫入文件

示例代碼:

procedure TForm1.Button1Click(Sender: TObject); 
const 
    cFilename = 'myfile.txt'; 
var 
    fs : TFileStream; 
    gBOM : TBytes; 
    gStr : RawByteString; 
begin 
    fs := TFileStream.Create(cFilename, fmCreate, fmShareDenyWrite); 
    try 
    gBOM := TEncoding.UTF8.GetPreamble; 
    fs.WriteBuffer(PAnsiChar(gBOM)^, Length(gBOM)); 

    // Dummy data 
    gStr := UTF8Encode('Dummy string') + AnsiChar(#13) + AnsiChar(#10); 
    fs.WriteBuffer(PAnsiChar(gStr)^, Length(gStr)); 

    // If you read the file now the BOM will be present, however 
    // the follow line appears to remove it. 
    FileSetAttr(cFilename, faReadOnly); 

    finally 
    FreeAndNil(fs); 
    end; 
end; 
+0

,你說,你打電話之前'FileSetAttr',Windows資源管理器顯示文件的大小爲17個字節,但經過:

試試這個:

procedure TForm1.Button1Click(Sender: TObject); const cFilename = 'c:\path to\myfile.txt'; var sw : TStreamWriter; Attrs: Integer; begin sw := TStreamWriter.Create(cFilename, False, TEncoding.UTF8); try sw.WriteLine('Dummy string'); finally sw.Free; end; Attrs := FileGetAttr(cFilename); if Attrs <> -1 then FileSetAttr(cFilename, Attrs or faReadOnly); end; 

或者那個調用,它顯示文件是14個字節?如果在關閉文件之後而不是在之前更改文件屬性,您是否觀察到任何行爲差異? –

+0

您描述的行爲不會發生。 –

+0

由於您已經打開了一個文件句柄,因此您應該使用Windows API函數'SetFileInformationByHandle'來設置屬性。但我認爲這並不重要。 –

回答

4

設置文件屬性對文件的現有內容沒有任何影響。 BOM可以消失的唯一方法是將文件的內容複製到省略了BOM的新文件中。設置屬性不這樣做。

請記住,您正在使用相對文件路徑,因此可能您的計算機上可能有多個文件副本,並且正在查看錯誤的文件。請始終使用完整路徑。

將一個BOM和文本寫入TEncoding的文件中的一種更簡單的方法是使用TStreamWriter類代替。

您應該在關閉文件後調用FileSetAttr()以確保它實際生效,並且您在致電FileSetAttr()之前需要調用FileGetAttr()以確保現有屬性得到正確保存。所以

// GetFileInformationByHandle() is declared in Windows.pas, but SetFileInformationByHandle() is not! 

type 
    _FILE_INFO_BY_HANDLE_CLASS = ( 
    FileBasicInfo, 
    FileStandardInfo, 
    FileNameInfo, 
    FileRenameInfo, 
    FileDispositionInfo, 
    FileAllocationInfo, 
    FileEndOfFileInfo, 
    FileStreamInfo, 
    FileCompressionInfo, 
    FileAttributeTagInfo, 
    FileIdBothDirectoryInfo 
); 
FILE_INFO_BY_HANDLE_CLASS = _FILE_INFO_BY_HANDLE_CLASS; 

_FILE_BASIC_INFO = record 
    CreationTime: LARGE_INTEGER; 
    LastAccessTime: LARGE_INTEGER; 
    LastWriteTime: LARGE_INTEGER; 
    ChangeTime: LARGE_INTEGER; 
    FileAttributes: DWORD; 
end; 
FILE_BASIC_INFO = _FILE_BASIC_INFO; 

function SetFileInformationByHandle(hFile: THandle; FileInformationClass: FILE_INFO_BY_HANDLE_CLASS; lpFileInformation: Pointer; dwBufferSize: DWORD): BOOL; stdcall; external 'kernel32' delayed; 

procedure TForm1.Button1Click(Sender: TObject); 
const 
    cFilename = 'c:\path to\myfile.txt'; 
var 
    sw : TStreamWriter; 
    fi: TByHandleFileInformation; 
    bi: FILE_BASIC_INFO; 
    Attrs: Integer; 
    AttrsSet: Boolean; 
begin 
    AttrsSet := False; 

    sw := TStreamWriter.Create(cFilename, False, TEncoding.UTF8); 
    try 
    sw.WriteLine('Dummy string'); 

    if CheckWin32Version(6, 0) then 
    begin 
     if GetFileInformationByHandle(TFileStream(sw.BaseStream).Handle, fi) then 
     begin 
     bi.CreationTime.LowPart := fi.ftCreationTime.dwLowDateTime; 
     bi.CreationTime.HighPart := fi.ftCreationTime.dwHighDateTime; 

     bi.LastAccessTime.LowPart := fi.ftLastAccessTime.dwLowDateTime; 
     bi.LastAccessTime.HighPart := fi.ftLastAccessTime.dwHighDateTime; 

     bi.LastWriteTime.LowPart := fi.ftLastWriteTime.dwLowDateTime; 
     bi.LastWriteTime.HighPart := fi.ftLastWriteTime.dwHighDateTime; 

     bi.ChangeTime := bi.LastWriteTime; 

     bi.FileAttributes := fi.dwFileAttributes or FILE_ATTRIBUTE_READONLY; 
     AttrsSet := SetFileInformationByHandle(TFileStream(sw.BaseStream).Handle, FileBasicInfo, @bi, SizeOf(bi)); 
     end; 
    finally 
    sw.Free; 
    end; 

    if not AttrsSet then 
    begin 
    Attrs := FileGetAttr(cFilename); 
    if Attrs <> -1 then 
     FileSetAttr(cFilename, Attrs or faReadOnly); 
    end; 
end; 
相關問題