顯示具有 Delphi Lib 標籤的文章。 顯示所有文章
顯示具有 Delphi Lib 標籤的文章。 顯示所有文章

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 等方式封裝功能,以降低版本相依性。

2025年5月19日 星期一

常用函數

String

System

function Copy(S: String; Index: Integer; Count: Integer): string;

 

System.SysUtils

function StringReplace(const S, OldPattern, NewPattern: string; Flags: TReplaceFlags): string;
function FormatDateTime(const Format: string; DateTime: TDateTime): string;
function FormatFloat(const Format: string; Value: Extended): string;
function FormatCurr(const Format: string; Value: Currency): string;
function Trim(const S: string): string; 
function TrimLeft(const S: string): string; overload;
function TrimRight(const S: string): string; overload;
function QuotedStr(const S: string): string; overload;

 比對字串 (不區分大小寫)
function CompareText(const S1, S2: string): Integer; 
                    function SameText (const S1, S2:String):Boolean;  
 
function UpperCase(const S: string): string; 
function UpperCase(const S: string; LocaleOptions: TLocaleOptions): string;
function LowerCase(const S: string): string; overload;
function LowerCase(const S: string; LocaleOptions: TLocaleOptions): string;
function Languages: TLanguages;
function FormatFloat(const Format: string; Value: Extended): string; 


System.StrUtils

 回傳Text存在於Array裡的索引值 (區分大小寫)
function IndexStr(const AText: string; const AValues: array of string): Integer;

回傳Text存在於Array裡的索引值 (不區分大小寫)
function IndexText(const AText: string; const AValues: array of string): Integer; 
 
function LeftStr(const AText: string; const ACount: Integer): string; overload;
function RightStr(const AText: string; const ACount: Integer): string; overload;
function MidStr(const AText: string; const AStart, ACount: Integer): string; overload;
function ReverseString(const AText: string): string;
function SplitString(const S, Delimiters: string): TStringDynArray;
 
function IfThen(AValue: Boolean; const ATrue: string; AFalse: string = ''): string; overload;  

Integer / Float

System

是否為奇數
function Odd(X: Integer): Boolean;
 
procedure Inc(var X: Integer); 
procedure Inc(var X: Integer; N: Integer);

 

System.SysUtils

function StrToIntDef(const S: string; const Default: Extended): Extended;
function StrToFloatDef(const S: string; const Default: Extended): Extended;
function TryStrToFloat(const S: string; out Value: Extended): Boolean;
function TryStrToCurr(const S: string; out Value: Currency): Boolean;
function TryStrToInt(const S: string; out Value: Integer): Boolean; overload;

 

System.Math

平方
function Power(const Base, Exponent: Double): Double;
 
將變數向上捨入至正無窮大。
function Ceil(const X: Double): Integer; 
 
將變數向負無窮方向舍入。
function Floor(const X: Double): Integer;  
 
Form.Width / 2 回傳浮點數
Form.Width div 2 回傳整數值 (不計小數)

function Max(const A, B: Integer): Integer; overload;
function MaxValue(const Data: array of Double): Double; overload;
function MaxIntValue(const Data: array of Integer): Integer;
function Min(const A, B: Integer): Integer; overload;
function MinValue(const Data: array of Double): Double; overload;
function MinIntValue(const Data: array of Integer): Integer;
function InRange(const AValue, AMin, AMax: Int64): Boolean; overload;

取整數
function Int(const X: Extended): Extended;

取小數
function Frac(const X: Extended): Extended;

判斷正/負號
function Sign(const AValue: Integer): TValueSign; 

Array中的平均值
function Mean(const Data: array of Single): Single;

function IfThen(AValue: Boolean; const ATrue: Integer; const AFalse: Integer = 0): Integer; overload;


Datetime

System.DateUtils

function IsPM(const AValue: TDateTime): Boolean; 
function IsAM(const AValue: TDateTime): Boolean;
function IsValidDate(const AYear, AMonth, ADay: Word): Boolean;
 
傳回指定 TDateTime 值所在年份的週數。
function WeeksInYear(const AValue: TDateTime): Word;
 
傳回指定年份的週數。
function WeeksInAYear(const AYear: Word): Word;       
 
傳回指定 TDateTime 值所在年份的天數。
function DaysInYear(const AValue: TDateTime): Word; 
 
傳回指定年份的天數。
function DaysInAYear(const AYear: Word): Word; 
 
傳回指定月份的天數。
function DaysInMonth(const AValue: TDateTime): Word;
 
傳回指定年份的指定月份的天數。
function DaysInAMonth(const AYear, AMonth: Word): Word;
 
function Today: TDateTime;
function Yesterday: TDateTime;
function Tomorrow: TDateTime;
 
function YearOf(const AValue: TDateTime): Word;
function MonthOf(const AValue: TDateTime): Word;
function WeekOf(const AValue: TDateTime): Word;           
function DayOf(const AValue: TDateTime): Word;
function HourOf(const AValue: TDateTime): Word;
function MinuteOf(const AValue: TDateTime): Word;
function SecondOf(const AValue: TDateTime): Word;
function MilliSecondOf(const AValue: TDateTime): Word;
function StartOfTheMonth(const AValue: TDateTime): TDateTime;
function EndOfTheMonth(const AValue: TDateTime): TDateTime;
function StartOfAMonth(const AYear, AMonth: Word): TDateTime;
function EndOfAMonth(const AYear, AMonth: Word): TDateTime;
function StartOfTheWeek(const AValue: TDateTime): TDateTime; 
function EndOfTheWeek(const AValue: TDateTime): TDateTime;  
function StartOfAWeek(const AYear, AWeekOfYear: Word;const ADayOfWeek: Word = 1): TDateTime;
function EndOfAWeek(const AYear, AWeekOfYear: Word;const ADayOfWeek: Word = 7): TDateTime;
 
function YearsBetween(const ANow, AThen: TDateTime): Integer;
function MonthsBetween(const ANow, AThen: TDateTime): Integer;
function WeeksBetween(const ANow, AThen: TDateTime): Integer;
function DaysBetween(const ANow, AThen: TDateTime): Integer;
function HoursBetween(const ANow, AThen: TDateTime): Int64;
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
function SecondsBetween(const ANow, AThen: TDateTime): Int64;
function MilliSecondsBetween(const ANow, AThen: TDateTime): Int64;
 
function IncYear(const AValue: TDateTime; const ANumberOfYears: Integer = 1): TDateTime; inline;
function IncWeek(const AValue: TDateTime;const ANumberOfWeeks: Integer = 1): TDateTime; inline;

指定天數偏移的日期 
function IncDay(const AValue: TDateTime; const ANumberOfDays: Integer = 1): TDateTime; inline;
 
function IncHour(const AValue: TDateTime; const ANumberOfHours: Int64 = 1): TDateTime; inline;
function IncMinute(const AValue: TDateTime; const ANumberOfMinutes: Int64 = 1): TDateTime; 
function IncSecond(const AValue: TDateTime; const ANumberOfSeconds: Int64 = 1): TDateTime;
function IncMilliSecond(const AValue: TDateTime; const ANumberOfMilliSeconds:Int64 = 1): TDateTime;


File/Fold

System.SysUtil

資料夾是否存在.
function DirectoryExists(const Directory: string; FollowLink: Boolean = True): Boolean;

檔案是否存在
function FileExists(const FileName: string; FollowLink: Boolean = True): Boolean;

檔案放置的資料夾
function ExtractFileDir(const FileName: string): string;

檔案放置的路徑
function ExtractFilePath(const FileName: string): string;

檔案放置的磁碟代號
function ExtractFileDrive(const FileName: string): string;

檔案名稱
function ExtractFileName(const FileName: string): string;

變更副檔名
function ChangeFileExt(const FileName, Extension: string): string;

檔案是否唯讀
function FileIsReadOnly(const FileName: string): Boolean;

檔案設定唯讀
function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean;

刪除檔案
function DeleteFile(const FileName: string): Boolean;

檔案名稱更名
function RenameFile(const OldName, NewName: string): Boolean;

檔案搜尋
function FileSearch(const Name, DirList: string): string;

目前的使用路徑
function GetCurrentDir: string;

設定目前的使用路徑
function SetCurrentDir(const Dir: string): Boolean;

建立資料夾(樹狀)
function ForceDirectories(Dir: string): Boolean;

建立資料夾
function CreateDir(const Dir: string): Boolean;

移除資料夾
function RemoveDir(const Dir: string): Boolean;


2024年6月13日 星期四

Delphi 在桌面產生捷徑

uses
  Windows, SysUtils, ComObj, ShlObj;

procedure CreateShortcutOnDesktop(const TargetPath, ShortcutName: string);
var
  WSHShell: Variant;
  Shortcut: Variant;
  DesktopPath: array[0..MAX_PATH] of Char;
begin
  // 初始化 WSHShell
  WSHShell := CreateOleObject('WScript.Shell');
  // 獲取桌面路徑
  SHGetSpecialFolderPath(0, DesktopPath, CSIDL_DESKTOP, False);
  // 創建捷徑對象
  Shortcut := WSHShell.CreateShortcut(IncludeTrailingPathDelimiter(DesktopPath) + ShortcutName + '.lnk');
  // 設置捷徑目標路徑和描述
  Shortcut.TargetPath := TargetPath;
  Shortcut.Description := 'Shortcut to ' + ShortcutName;
  // 保存捷徑
  Shortcut.Save;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // 創建 A.EXE 的捷徑到桌面
  CreateShortcutOnDesktop('C:\path\to\A.EXE', 'A.EXE Shortcut');
end;

2024年1月30日 星期二

System.Generics.Collections.TDictionary

 System.Generics.Collections.TDictionary



uses System.Generics.Collections;
...
 var dicDoctionary: TDictionary<String, String>   //Key, Value

//宣告
dicDoctionary :=  TDictionary<String, String>.Create;

//新增
dicDoctionary.Add(Key, Value);

//刪除
dicDoctionary.Remove(Key);

//新增或取代Value
dicDoctionary.AddOrSetValue(Key, Value);

//比對Key是否存在
dicDoctionary.ContainsKey(Key);

//比對Value是否存在
dicDoctionary.ContainsValue(Value);

//數量
dicDoctornary.Count;

//取值
var sValue:String;
dicDoctionary.TryGetValue(Key, sValue);

//
var sKey, sValue:String;
for sKey in dicDoctionary.Keys do
begin
  dicDoctionary.TryGetValue(sKey, sValue);
  ...
end;


2024年1月15日 星期一

DialogLib.pas 記錄

 

InputBox


EditConnectionString


FindDialog / ReplaceDialog


OpenDialog / SaveDialog


Waiting Form



2023年12月25日 星期一

PopupMenuLib.pas 記錄


AttachFilePopupMenu

procedure TForm1.FormCreate(Sender: TObject);
begin
  popAttachFile := TAttachFilePopupMenu.Create;
end;

procedure TForm1.Button9Click(Sender: TObject);
begin
  popAttachFile.Popup(Button9);  //參數 nil  會在游標處展開下拉選單
end;

//Property
//  popAttachFile.AttachFiles       //附加的文件清單
//  popAttachFile.Readonly          //唯讀,不可附加、移除
//  popAttachFile.DisableAttachFile //不提供附加
//  popAttachFile.DisableRemove     //不提供移除
//  popAttachFile.DisableOpenFile   //不提供文件開啟


PrinterPopupMenu

Uses Printers;

procedure TForm1.Button9Click(Sender: TObject);
var pmPrinterList: TPrinterPopupMenu;
begin
   pmPrinterList := TPrinterPopupMenu.Create;
   pmPrinterList.Popup(Button9);  //參數 nil  會在游標處展開下拉選單
end;

// Property
//   pmPrinterList.PrinterIndex   //印表機Index
//   pmPrinterList.PrinterName    //印表機名稱



ControlLib.pas 記錄








TColor 調整亮度

procedure pr_BrightenColor(var Color: TColor; Brightness: Integer);
var R, G, B: Byte;
begin
  R := GetRValue(Color);
  G := GetGValue(Color);
  B := GetBValue(Color);

  R := Min(255, R + Brightness);
  G := Min(255, G + Brightness);
  B := Min(255, B + Brightness);

  Color := RGB(R, G, B);
end;


2023年11月28日 星期二

在表單底下空白處,使用LoopBand填滿表格

報表情境1 (未使用GroupBand)


procedure TForm1.DetailBand1AfterPrint(Sender: TQRCustomBand;  BandPrinted: Boolean);
var iCurrencyY, iPaperLength, iPrintY, iCount, iLoopBandHeight, iBottomMargin,
  iPageFooterBand, iQRGroupFooter:Integer;
begin
  //檢查最後一筆
  if ADOQuery1.RecNo=ADOQuery1.RecordCount then
  begin
    iPaperLength := QuickRep1.QRPrinter.PaperLength;  //報表高度
    iCurrencyY := QuickRep1.CurrentY;  //目前輸出的高度
    iLoopBandHeight := Ceil(QRLoopBand1.Size.Length);  //LoopBand 的高度
    iPageFooterBand := Ceil(PageFooterBand1.Size.Length);  //PageFooterBand 的高度
    iBottomMargin := Ceil(QuickRep1.Page.BottomMargin);  //報表邊界Bottom的高度
    iPrintY := iPaperLength - iCurrencyY - iPageFooterBand - iBottomMargin; //LoopBand 輸出的高度
    iCount := iPrintY div iLoopBandHeight;
    QRLoopBand1.PrintCount := iCount;
end;




 報表情境2 (使用GroupBand)



procedure TForm1.QuickRep2StartPage(Sender: TCustomQuickRep);
begin
  //在報表上放置QRExpr1,借來運算QRGroup.Expression運算後的結果
  QRExpr1.Expression := QRGroup2.Expression;
  QRExpr1.Enabled := False;
end;


procedure TForm1.DetailBand1AfterPrint(Sender: TQRCustomBand; BandPrinted: Boolean);
var iCurrencyY, iPaperLength, iPrintY, iCount, iLoopBandHeight, iBottomMargin,
  iPageFooterBand, iQRGroupFooter:Integer;
  sCurValue, sNextValue:String;
begin
  //取得目前以及下一筆資料的運算結果做比對
  sCurValue := QRExpr1.Value.StringVal;
  adoQuery1.Next;
  sNextValue := sCurValue;
  if not adoQuery1.eof then
  begin
    sNextValue := QRExpr1.Value.StringVal;
    adoquery1.Prior;
  end;

  //如果是最後一筆,或是前後筆資料不吻合,就使用LoopBand填滿
  iCount := 0;
  if (ADOQuery1.RecNo=ADOQuery1.RecordCount) or (sCurValue<>sNextValue) then
  begin
    iPaperLength := QuickRep2.QRPrinter.PaperLength;  //報表高度
    iCurrencyY := QuickRep2.CurrentY;  //目前輸出的高度
    iLoopBandHeight := Ceil(QRLoopBand2.Size.Length);  //LoopBand 的高度
    iQRGroupFooter := Ceil(QRGroupFooter2.Size.Length);  //GroupFooter 的高度
    iPageFooterBand := Ceil(PageFooterBand2.Size.Length);  //PageFooter 的高度
    iBottomMargin := Ceil(QuickRep2.Page.BottomMargin);  //報表邊界Bottom的高度
    iPrintY := iPaperLength - iCurrencyY - iPageFooterBand - iQRGroupFooter - iBottomMargin;
    iCount := iPrintY div iLoopBandHeight;
  end;
  QRLoopBand2.PrintCount := iCount;
end;