2026年6月30日 星期二

Delphi 函式庫發佈方式(提供 Interface PAS + DCU)

 

如果想將程式工具提供他人使用,且不想提供程式原始碼,可以參考以下做法

Delphi 函式庫發佈方式(提供 Interface PAS + DCU)

一、目的

當 Delphi 開發完成一個 Unit,希望提供給其他開發者使用,但又不希望公開原始程式碼時,可以採用:

  • 公開 Interface PAS
  • 提供編譯完成的 DCU

使用者可以正常 uses 該 Unit,也能使用 IDE 的型別提示及程式碼完成(Code Insight),但無法看到真正的程式實作。


二、發佈內容

假設原始 Unit 名稱為:

MyLib.pas

發佈內容如下:

Release

├── MyLib.pas ← 只有 Interface 宣告
└── MyLib.dcu ← 編譯完成的程式

不提供完整原始碼。


三、原始 Unit

例如:

unit MyLib;

interface

uses
System.SysUtils;

type
TMyClass = class
public
constructor Create;
destructor Destroy; override;

function Add(A, B: Integer): Integer;
function Sub(A, B: Integer): Integer;
end;

function GetVersion: string;

implementation

constructor TMyClass.Create;
begin
inherited;
end;

destructor TMyClass.Destroy;
begin
inherited;
end;

function TMyClass.Add(A, B: Integer): Integer;
begin
Result := A + B;
end;

function TMyClass.Sub(A, B: Integer): Integer;
begin
Result := A - B;
end;

function GetVersion: string;
begin
Result := '1.0';
end;

end.

四、編譯產生 DCU

使用 Delphi 編譯後,會產生:

MyLib.dcu

真正執行的程式都在 DCU 內。


五、建立公開 PAS

建立另一份供發布使用的 MyLib.pas

注意:不要修改原始碼,而是另外建立一份。

例如:

Project

├── Source
│ MyLib.pas ← 原始完整程式

└── Release
MyLib.pas ← 公開介面
MyLib.dcu

公開 PAS 保留 Interface:

unit MyLib;

interface

uses
System.SysUtils;

type
TMyClass = class
public
constructor Create;
destructor Destroy; override;

function Add(A, B: Integer): Integer;
function Sub(A, B: Integer): Integer;
end;

function GetVersion: string;

implementation

end.

可以看到:

  • Class
  • Function
  • Procedure
  • Property
  • Event
  • Record
  • Enum

都需要保留。

但是:

所有 Implementation 內的程式碼全部刪除。


六、使用者如何使用

使用者只要:

uses
MyLib;

即可正常呼叫:

var
M: TMyClass;
begin
M := TMyClass.Create;
try
ShowMessage(IntToStr(M.Add(3,5)));
finally
M.Free;
end;
end;

不需要任何特殊設定。


七、IDE 功能

由於 Interface 仍存在,因此 Delphi IDE 可以提供:

  • Code Insight
  • Auto Complete
  • Parameter Hint
  • 型別檢查
  • 編譯檢查

使用體驗與一般 Unit 幾乎相同。


八、可以隱藏哪些內容

可以隱藏:

  • 所有演算法
  • 所有商業邏輯
  • SQL
  • 加解密流程
  • API 呼叫方式
  • 所有 Function 實作
  • 所有 Method 實作

仍然會看到:

  • Class 名稱
  • Function 名稱
  • Procedure 名稱
  • Property 名稱
  • Record 定義
  • Enum 定義
  • Event 定義

如果 Interface 中宣告了 Private 欄位:

private
FData: Integer;

使用者仍然可以看到:

FData

只是無法知道如何使用。

因此,如果希望降低資訊曝光,建議不要在 Interface 中放置過多內部欄位或實作細節。


九、優點

  1. 不公開原始碼。
  2. 使用方式與一般 Unit 完全相同。
  3. IDE 可正常提供 Code Insight。
  4. 不需要 DLL。
  5. 執行速度與一般 Delphi 程式相同。
  6. 發布方便。

十、缺點

1. 無法跨 Delphi 版本

DCU 為 Delphi 編譯器產生的中間檔。

不同 Delphi 版本的 DCU 格式可能不同,因此:

  • 無法保證相容
  • 通常不可共用

例如:

編譯版本使用版本是否可用
XE10XE10
XE10XE8
XE10Delphi 10.4
XE10Delphi 11
XE10Delphi 12

因此,每個 Delphi 版本都需要重新編譯對應的 DCU。


2. 每個版本都需要重新發布

若要支援:

  • XE10
  • 10.4 Sydney
  • 11 Alexandria
  • 12 Athens

通常需要:

Release

├── XE10
│ MyLib.pas
│ MyLib.dcu

├── 10.4
│ MyLib.pas
│ MyLib.dcu

├── 11
│ MyLib.pas
│ MyLib.dcu

└── 12
MyLib.pas
MyLib.dcu

3. 若公開介面有修改

例如新增:

function Test: Integer;

就需要重新:

  1. 編譯 DCU。
  2. 更新公開 PAS。
  3. 一起發布。

兩者必須保持一致。


十一、適用情況

適合:

  • 公司內部函式庫。
  • 不希望公開原始碼。
  • Delphi 開發團隊使用相同版本。
  • 商業 Delphi 函式庫。
  • 元件開發。

十二、不適合情況

若需要:

  • 支援 Delphi 多個版本
  • 支援 C++
  • 支援 C#
  • 支援 VB
  • 支援其他語言

則建議使用:

  • DLL
  • COM
  • Web API
  • REST API

而不是 DCU。


十三、建議

若所有使用者皆使用相同 Delphi 版本,採用 Interface PAS + DCU 是一種簡單且成熟的封裝方式,能兼顧開發便利性與原始碼保護。

若需支援不同 Delphi 版本,則必須針對每個版本重新編譯並發布對應的 .dcu單一 DCU 無法跨 Delphi 版本使用。如果目標是跨 Delphi 版本,建議直接提供相容的原始碼,或改以 DLL、COM、REST API 等方式封裝功能,以降低版本相依性。

2026年5月21日 星期四

Delphi Enum 轉字串

 

uses
  System.TypInfo;

type
  TDataState = (stInquiry, stNew, stEdit, stDelete, stRecall);

function DataStateToString(AState: TDataState): string;
begin
  Result := GetEnumName(TypeInfo(TDataState), Ord(AState));
end;



2026年3月23日 星期一

Delphi FireDAC

 FDManager

var sParams:TStringList;

sParams.Add('Server=xx.xx.xx.xx');
sParams.Add('User_Name=xxxxx');
sParams.Add('Password=xxxxx');
sParams.Add('Database=xxxxx');
sParams.Add('DriverID=MSSQL');
sParams.Add('Pooled=True');
          sParams.Add('ExtendedMetadata=True');  //可以用來取得欄位屬性歸屬
FDManager1.AddConnectionDef('MSSQL_Pool', 'MSSQL', sParams);

FDConnection (與FDManager搭配使用,可以理解是DB Session)

FDConnection1.ConnectionDefName := 'MSSQL_Pool';
FDConnection1.Connected := True;

FDQuery / FDUpdateSQL

with FDQuery1 do
begin
  Connection := FDConnection1;
  CachedUpdates := True;
  UpdateOptions.UpdateTableName := 'Table1';
  UpdateOptions.KeyFields := 'Field1,Field2';
  UpdateOptions.UpdateMode := upWhereKeyOnly;
 
            UpdateObject := Self.FDUpdateSQL1;

  Close;
  SQL.Text :=
    ' select a.*, b.Fiedl5 '+
    ' from Table1 a '+
    ' left join Table2 b on b.field1=a.field1 '+
    ' where ... ';
  Open;
end;

ApplyUpdates

FDQuery1.CheckBrowseMode;
FDQuery1.FetchNext;  //抓資料到本地快取, 確保快取內容有提供回寫的資料
FDQuery1.ApplyUpdates(-1);


取得別名欄位的表格屬性及原欄名稱,需設定連線參數 ExtendedMetadata=True
var column:TFDDatSColumn;
column := FDQuery1.GetFieldColumn(Field);
column.ActualOriginTabName // Field歸屬的Table
column.ActualOriginColName // Field實體表格中的Column Name


FDConnection -> FDQuery,當FDQuery資料是分批讀取還沒完全將資料載入時,FDConnection沒辦法被其他FDQuery操作使用,會出現以下的錯誤訊息。


FDQuery.SourceEOF; //可以知道資料是否已完全載入

提供的建議做法是放二個FDConnection,一個做 Select...,另一個做Update/Insert/Delete/Exec ...
如果沒有閒置的 FDConnection, 就要Create一個FDConnection提供給FDQuery使用。





2026年3月19日 星期四

美元 為何又稱美金

 「美元」之所以在華語世界常被稱為「美金」,主要與歷史上的貨幣制度以及黃金的關聯性有關。這可以追溯到 19 世紀到 20 世紀中葉的全球金融背景:

1. 金本位制度(Gold Standard)

在 19 世紀後半葉至 20 世紀初,世界主要經濟體實行「金本位制」。當時的美元與黃金是直接掛鉤的,面額可以隨時向銀行兌換成等值的實體黃金。

實質等同: 由於美元在當時代表著特定重量的黃金,民眾習慣將這種具有黃金價值的貨幣稱為「美金」。

2. 布列頓森林體系(Bretton Woods System)

二戰結束後(1944 年),全球建立了布列頓森林體系。該體系確立了美元與黃金掛鉤(每 35 美元兌換 1 盎司黃金),而其他國家的貨幣再與美元掛鉤。

國際儲備: 美元成為全球最主要的儲備貨幣,地位如同黃金般穩固。這進一步加深了「美元即黃金」的社會認知,「美金」這個稱呼也因此在兩岸三地及海外華人圈廣為流傳。

3. 文化與習慣

雖然 1971 年尼克森衝擊(Nixon Shock)宣告美元正式脫離金本位,美元不再能兌換黃金,但「美金」這個詞彙已經深植於語言習慣中:

辨識度: 在華語口語中,「美金」聽起來比「美元」更具備財富的象徵意義。

區分需求: 在早期外匯管制較嚴格的時期,民間習慣用「美金」來區分本地貨幣與這種強勢的國際貨幣。

補充小知識:

儘管現在美元本質上是「法定貨幣」(Fiat Money)而非金幣,但這種稱呼已成為一種文化印記。在正式的金融公文或新聞報導中,通常會使用「美元」;而在日常生活中,「美金」依然是極為普遍的用法。


Gemini

 

2026年2月25日 星期三

修正 Quick Report 預覽/列印的顯示比例

 當調整了電腦螢幕的縮放比例後,操作程式裡的報表QuickReport,發現報表預覽的資料內容沒有隨著顯示縮放比跟著做調整,但不影響實際列印輸出的結果。



參考網上的作法,修正 QuickReport 需 QRPrntr.pas 排除縮放比的問題。

我採用網友提供的方法1來處理。

File Name : QRPrntr.pas

Procedure Name : CreateMetafileCanvas


QRPrntr.pas 修正後,重新編譯QR506RunDXE10.bpl,

預覽結果就會以符合系統縮放比做調整了。


【參考連結】

老森常譚 IT Help 《Delphi》修正 Quick Report 預覽列印的比例問題


2025年12月25日 星期四

QuickReport - 載入 Qrp 報表文件時,會預先使用預設印表機的紙張格式套用在文件上,與設計的報表格式不符...

 var 
  repReport:TQuickRep;
  iPageHeightPixel, iPageWidthPixel:Integer;
  Meta: TMetafile;
begin
  inherited;
  repReport := TQuickRep.Create(nil);
  repReport.PrevInitialZoom := qrZoomToWidth;   //頁寬
  repReport.PrevShowThumbs := False;            //不顯示簡視欄
  repReport.PrevShowSearch := False;            //不顯示搜尋欄
  repReport.PreviewInitialState := wsMaximized; //最大化
  repReport.ShowProgress := True;
  repReport.PreviewDefaultSaveType := stQRP;

  repReport.Prepare;
  repReport.QRPrinter.Load('Report.qrp');    //載入檔案

  Meta := repReport.QRPrinter.GetPage(1);
  iPageWidthPixel := Meta.Width;    //取得文件記錄中的尺寸 Pixel
  iPageHeightPixel := Meta.Height;  //取得文件記錄中的尺寸 Pixel

  repReport.Page.Orientation := repReport.QRPrinter.Orientation; //報表直/橫向
  repReport.Page.PaperSize := TQRPaperSize.Custom; //報表紙張格式
  repReport.Units := TQRUnit.Pixels;
  repReport.Page.Length := iPageHeightPixel; //設定報表長度
  repReport.Page.Width := iPageWidthPixel;   //設定報表寬度

  repReport.PrinterSettings.PaperSize := TQRPaperSize.Custom;  //報表紙張格式
  repReport.PrinterSettings.PrinterIndex := 1;      //指定印表機
  repReport.PrinterSettings.ApplySettings(repReport.QRPrinter);

  repReport.QRPrinter.PrinterIndex := 1;            //指定印表機
  repReport.QRPrinter.aPrinterSettings.ApplySettings;
  repReport.QRPrinter.PreviewModal;      //預覽文件
end;

2025年12月18日 星期四

QuickReport Prepare / Preview / Print Event觸發的差異




 

面對風險、機遇和挑戰,你為什麽總是賺小虧大?


 

高智商問題:100名囚犯如何用數學拯救自己?

 


透過詢問專業建議,更能具體掌握需要改進細節

 


#CharGPT

在參與考試或競賽後,建立回饋機制對個人成長至關重要。不論最終的成績表現如何,主動向評分委員請益都是提升自我的關鍵步驟。透過詢問專業建議,參賽者能具體掌握需要改進的空間或加強的細節。這種積極的學習態度有助於發現盲點,進而在未來的挑戰中展現更優異的實力。總結而言,將賽後的檢討視為持續進步的契機,是追求卓越不可或缺的過程。

2025年6月11日 星期三

QuickReport 調整 QRShape 配合 DetailBand 展延

 


procedure TForm1.DetailBand1BeforePrint(Sender: TQRCustomBand; var PrintBand: Boolean);
var h:extended;
begin
  DetailBand1.ExpandedHeight(h);
  QRShape1.Size.Height := h - QRShape1.Size.Top - 1;
end;



2025年5月21日 星期三

藍寶石

 藍寶石

「藍寶石」雖然名字有「藍」字,但實際上藍寶石的顏色不只藍色,這是因為它的命名來自於寶石的礦物成分,而不是顏色。

✅ 原因說明:
1. 藍寶石是「剛玉」(Corundum)的一種
  • 剛玉的化學成分是氧化鋁(Al₂O₃)。
  • 純淨的剛玉其實是無色透明的。
  • 當剛玉中含有不同的微量元素時,會呈現不同的顏色。

2. 顏色來源是微量元素
微量元素 顏色
鐵、鈦         藍色
                紅色 → 就是「紅寶石」
                黃色
鉻 + 鐵         粉紅色
                綠色

3. 命名方式
所有不是紅寶石的剛玉(即使不是藍色),在寶石學上都歸類為「藍寶石」的一種。

  • 比如:黃色叫「黃寶石」實際上是「黃色藍寶石」。
  • 粉色叫「粉紅藍寶石」、綠色叫「綠色藍寶石」。

❗那為什麼紅色的剛玉叫「紅寶石」?
這是因為紅寶石的價值和歷史地位非常特殊,因此被單獨劃分出來。它和藍寶石其實是同一種礦物(剛玉),但名稱上有所區分。

🔍 總結
「藍寶石」是礦物名稱,不代表一定是藍色。
各種顏色的剛玉都可以叫「藍寶石」,除了紅色那種叫「紅寶石」。
因此你會看到粉色、黃色甚至無色的「藍寶石」。

ChatGPT