首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi INI 檔案讀寫操作完整指南:Windows API 與 TIniFile 雙重實作

     編輯:Delphi

前言

INI 檔案仍是輕量級設定儲存的常見選擇。Delphi 提供兩種主要操作方式:Windows API 直接呼叫 (GetPrivateProfileString 等) 與 VCL 封裝類別 (TIniFile/TMemIniFile)。本文完整對比兩種用法、參數詳解、編碼處理、效能差異與最佳實務。

方式一:Windows API 直接呼叫 (Kernel32.dll)

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 參數詳解

API關鍵參數說明
GetPrivateProfileStringnSize緩衝區大小 (字元數),含結束符。傳回值為實際複製字元數 (不含結束符)。緩衝區太小會截斷。
lpDefault找不到時回傳值,**不可為 nil** (會導致存取違規),需至少為空字串 ''。
WritePrivateProfileStringlpString = nil刪除 Key (指定 Section/Key) 或 Section (Key 為 nil)。
lpFileName完整路徑。相對路徑會在 Windows 目錄尋找。**建議永遠使用完整路徑**。

方式二:TIniFile / TMemIniFile (VCL 封裝,推薦)

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 vs TMemIniFile 比較

特性TIniFileTMemIniFile
基礎實作直接呼叫 Windows API記憶體快取,析構/UpdateFile 時批次寫入
讀取效能每次讀取磁碟記憶體讀取,極快
寫入效能每次寫入磁碟 (同步)記憶體修改,批次寫入
多執行緒/進程安全較好 (API 層級鎖定)需自行同步,UpdateFile 時可能覆蓋他人修改
適用場景單一進程、低頻讀寫、需即時生效高頻讀寫、大量設定、啟動時載入/關機存檔

編碼與 Unicode 處理 (關鍵)

Delphi 2009+ (Unicode 版本)
TIniFile 內部使用 Wide 版 API (GetPrivateProfileStringW),INI 檔案**必須儲存為 UTF-16 LE (含 BOM)** 或 UTF-8 (無 BOM 可能誤判)。建議明確指定編碼:
Ini := TMemIniFile.Create(FileName, TEncoding.UTF8); (Delphi 10.3+ 支援)
ANSI 相容舊版 INI
若需讀寫 ANSI 編碼 INI (舊版 Delphi 產生),使用 TIniFile.Create(FileName, TEncoding.ANSI) 或 TEncoding.GetEncoding(950) (繁體中文 Big5)。
手動處理編碼
直接呼叫 API 時,PChar 為 PWideChar,系統會依檔案 BOM 自動判斷。無 BOM UTF-8 可能被誤判為 ANSI 導致亂碼。

進階技巧

1. 類型安全的區塊存取 (Record 映射)

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;

2. 加密敏感欄位

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)

3. 檔案鎖定與並發

多進程同時寫入同一 INI 可能損毀。解決方案:

  • 使用 TMemIniFile + TFileStream 獨佔鎖定手動實作
  • 改用 SQLite (TDataSet 介面) 取代 INI,原生支援 ACID
  • 採用登錄表 (TRegistry) 系統級同步

最佳實務總結

  1. 優先使用 TMemIniFile:記憶體操作快、支援 UpdateFile 原子寫入、編碼可控。
  2. 明確指定編碼:TMemIniFile.Create(FileName, TEncoding.UTF8)。
  3. 完整路徑:ChangeFileExt(Application.ExeName, '.ini') 或 GetAppDataFolder。
  4. 封裝存取層:建立 TSettingsManager 單例,隱藏 INI 細節,提供強型別屬性。
  5. 敏感資料加密:密碼、Token 必須加密儲存。
  6. 版本遷移:ReadInteger('Version', 0) 判斷結構版本,自動升級舊設定。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved