INI 檔案仍是輕量級設定儲存的常見選擇。Delphi 提供兩種主要操作方式:Windows API 直接呼叫 (GetPrivateProfileString 等) 與 VCL 封裝類別 (TIniFile/TMemIniFile)。本文完整對比兩種用法、參數詳解、編碼處理、效能差異與最佳實務。
uses Winapi.Windows, System.SysUtils;
{ 讀取字串 }
function ReadIniString(const Section, Key, Default, FileName: string): string;
var
Buffer: array[0..1023] of Char;
Len: DWORD;
begin
Len := GetPrivateProfileString(PChar(Section), PChar(Key), PChar(Default),
Buffer, Length(Buffer), PChar(FileName));
SetString(Result, Buffer, Len);
end;
{ 讀取整數 }
function ReadIniInteger(const Section, Key: string; Default: Integer; const FileName: string): Integer;
begin
Result := GetPrivateProfileInt(PChar(Section), PChar(Key), Default, PChar(FileName));
end;
{ 寫入字串 }
function WriteIniString(const Section, Key, Value, FileName: string): Boolean;
begin
Result := WritePrivateProfileString(PChar(Section), PChar(Key), PChar(Value), PChar(FileName));
end;
{ 刪除 Key }
function DeleteIniKey(const Section, Key, FileName: string): Boolean;
begin
Result := WritePrivateProfileString(PChar(Section), PChar(Key), nil, PChar(FileName));
end;
{ 刪除 Section }
function DeleteIniSection(const Section, FileName: string): Boolean;
begin
Result := WritePrivateProfileString(PChar(Section), nil, nil, PChar(FileName));
end;
{ 讀取所有 Section 名稱 }
procedure ReadIniSections(const FileName: string; Strings: TStrings);
var
Buffer: array[0..16383] of Char;
Len: DWORD;
P: PChar;
begin
Len := GetPrivateProfileString(nil, nil, '', Buffer, Length(Buffer), PChar(FileName));
P := Buffer;
while (P^ <> #0) and (Len > 0) do
begin
Strings.Add(P);
Inc(P, StrLen(P) + 1);
Dec(Len, StrLen(P) + 1);
end;
end;
| API | 關鍵參數 | 說明 |
|---|---|---|
GetPrivateProfileString | nSize | 緩衝區大小 (字元數),含結束符。傳回值為實際複製字元數 (不含結束符)。緩衝區太小會截斷。 |
lpDefault | 找不到時回傳值,**不可為 nil** (會導致存取違規),需至少為空字串 ''。 | |
WritePrivateProfileString | lpString = nil | 刪除 Key (指定 Section/Key) 或 Section (Key 為 nil)。 |
lpFileName | 完整路徑。相對路徑會在 Windows 目錄尋找。**建議永遠使用完整路徑**。 |
uses System.IniFiles, System.SysUtils;
procedure DemoIniFile;
var
Ini: TIniFile;
FileName: string;
begin
FileName := ChangeFileExt(Application.ExeName, '.ini');
// TIniFile: 即時寫入磁碟
// TMemIniFile: 記憶體操作,需呼叫 UpdateFile 寫入,效能較好、適合頻繁讀寫
Ini := TMemIniFile.Create(FileName);
try
// 寫入
Ini.WriteString('Database', 'Server', 'localhost');
Ini.WriteInteger('Database', 'Port', 5432);
Ini.WriteBool('Database', 'SSL', True);
Ini.WriteFloat('Settings', 'Version', 1.5);
Ini.WriteDateTime('Settings', 'LastUpdate', Now);
// 寫入二進位資料 (Base64 編碼)
Ini.WriteBinaryStream('Security', 'Certificate', CertStream);
// 讀取
Server := Ini.ReadString('Database', 'Server', '127.0.0.1');
Port := Ini.ReadInteger('Database', 'Port', 5432);
UseSSL := Ini.ReadBool('Database', 'SSL', False);
// 列舉
Ini.ReadSections(ListBox1.Items);
Ini.ReadSection('Database', ListBox1.Items);
Ini.ReadSectionValues('Database', ListBox1.Items); // Key=Value 格式
// 刪除
Ini.DeleteKey('Database', 'OldKey');
Ini.EraseSection('ObsoleteSection');
// 重要:TMemIniFile 必須呼叫 UpdateFile 才會寫入磁碟
Ini.UpdateFile;
finally
Ini.Free;
end;
end;
| 特性 | TIniFile | TMemIniFile |
|---|---|---|
| 基礎實作 | 直接呼叫 Windows API | 記憶體快取,析構/UpdateFile 時批次寫入 |
| 讀取效能 | 每次讀取磁碟 | 記憶體讀取,極快 |
| 寫入效能 | 每次寫入磁碟 (同步) | 記憶體修改,批次寫入 |
| 多執行緒/進程安全 | 較好 (API 層級鎖定) | 需自行同步,UpdateFile 時可能覆蓋他人修改 |
| 適用場景 | 單一進程、低頻讀寫、需即時生效 | 高頻讀寫、大量設定、啟動時載入/關機存檔 |
TIniFile 內部使用 Wide 版 API (GetPrivateProfileStringW),INI 檔案**必須儲存為 UTF-16 LE (含 BOM)** 或 UTF-8 (無 BOM 可能誤判)。建議明確指定編碼:Ini := TMemIniFile.Create(FileName, TEncoding.UTF8); (Delphi 10.3+ 支援)TIniFile.Create(FileName, TEncoding.ANSI) 或 TEncoding.GetEncoding(950) (繁體中文 Big5)。PChar 為 PWideChar,系統會依檔案 BOM 自動判斷。無 BOM UTF-8 可能被誤判為 ANSI 導致亂碼。type
TDatabaseConfig = record
Server: string;
Port: Integer;
Username: string;
Password: string;
UseSSL: Boolean;
class function Load(const Ini: TIniFile; const Section: string): TDatabaseConfig; static;
procedure Save(const Ini: TIniFile; const Section: string);
end;
class function TDatabaseConfig.Load(const Ini: TIniFile; const Section: string): TDatabaseConfig;
begin
Result.Server := Ini.ReadString(Section, 'Server', 'localhost');
Result.Port := Ini.ReadInteger(Section, 'Port', 5432);
Result.Username := Ini.ReadString(Section, 'Username', '');
Result.Password := Ini.ReadString(Section, 'Password', '');
Result.UseSSL := Ini.ReadBool(Section, 'UseSSL', False);
end;
procedure TDatabaseConfig.Save(const Ini: TIniFile; const Section: string);
begin
Ini.WriteString(Section, 'Server', Server);
Ini.WriteInteger(Section, 'Port', Port);
Ini.WriteString(Section, 'Username', Username);
Ini.WriteString(Section, 'Password', Password);
Ini.WriteBool(Section, 'UseSSL', UseSSL);
end;
uses System.Hash, System.NetEncoding;
function EncryptValue(const Value, Key: string): string;
var
HMAC: THMAC_SHA256;
begin
HMAC := THMAC_SHA256.Create(Key);
try
Result := TNetEncoding.Base64.EncodeBytesToString(HMAC.HashValue(TEncoding.UTF8.GetBytes(Value)));
finally
HMAC.Free;
end;
end;
// 寫入時加密
Ini.WriteString('DB', 'Password', EncryptValue(RealPassword, MasterKey));
// 讀取時解密 (需對稱加密,此處示意 HMAC 驗證用途,實際加密請用 AES)
多進程同時寫入同一 INI 可能損毀。解決方案:
TMemIniFile + TFileStream 獨佔鎖定手動實作TDataSet 介面) 取代 INI,原生支援 ACIDTRegistry) 系統級同步TMemIniFile:記憶體操作快、支援 UpdateFile 原子寫入、編碼可控。TMemIniFile.Create(FileName, TEncoding.UTF8)。ChangeFileExt(Application.ExeName, '.ini') 或 GetAppDataFolder。TSettingsManager 單例,隱藏 INI 細節,提供強型別屬性。ReadInteger('Version', 0) 判斷結構版本,自動升級舊設定。