建立 C&C++ 語言函式庫(for TC/BC)

摘要:建立 C&C++ 語言函式庫(for TC/BC)

這是在 1995 年整理後,2001 年再整理給實驗室使用的文件。文中所提的技巧,在目前最新的IDE可能是用不到了,不過,以 BCB 來說,一樣都有提供這些 DOS 指令的工具。而對於一般函式轉為函式庫的函式的一些說明,對現在來說,應該也還是可套用的方式,提供有需要的人參考囉。

┌─────────────────────────────────────┐
│ ○ IP 名稱 : 建立 C&C++ 語言函式庫 │
│ 改版人 : 李棟樑 <dllee@iname.com> │
│ 改版日期 : 2001-05-09 │
│ 版本 : 0.5 │
│ 修改說明 : 主要新增 include 檔中有整體外部變數時應有的寫法。 │
│ │
│ 改版人 : 李棟樑 <dllee@iname.com> │
│ 日期 : 2000-02-21 │
│ 版本 : 0.4 │
│ 開發人 : 李棟樑 <dllee@iname.com> │
│ 日期 : 1995-06-26 │
│ 版本 : 0.1 │
│ │
│ ○ 關鍵字 : C/C++,函式庫,檔頭,Library,include,.h,.hpp,.lib,Debug, │
│ TLib,make │
│ │
│ ○ 摘要: │
│ 本 IP 說明如何建立 C/C++ 的函式庫,以便他人可以方便地使用你所發展的函 │
│ 式,此外,也介紹如何在寫程式時減少出錯的方法及一個除錯工具的函式庫。 │
│ 內容包含: │
│ │
│ ■ 如何使用 Debug.Lib ── 一個除錯工具的函式庫 │
│ │
│ ■ 如何將 Subroutine 轉成 Library 的型式 │
│ │
│ ■ 使用 TLib 工具程式,造出 Library 檔 │
│ │
│ ■ 如何使用 Make 工具 ── C/C++ 語言提供的工具 │
│ │
│ ■ 提供 Library 用的 header 檔(include 檔)之寫法 │
│ │
│ ■ 使用 Library 的方法 │
│ │
│ ■ Library 說明檔應有的內容 │
│ │
│ 在最後一項「Library 說明檔應有的內容」明確規範了一個函式庫說明文件 │
│ 應用的內容,希望實驗室所開發的函式能按此規範。 │
│ 在建立 Library 的部分,有些可能已經是過期的用法了,不過,其觀念是相同 │
│ 的。 │
│ │
│ ○ 說明文件 : index00.txt 本 IP 的檔頭 │
│ keyword.txt 本 IP 的關鍵字檔 │
│ index.txt 本 IP 主要說明文件 │
│ index.txt.fp index.txt 的 Fine Printer 列表檔 │
│ old\MAKECLIB.TXT 0.3 版 │
│ old\MAKECLIB.TX2 0.3 版 番外篇 │
│ old\MAKECLIB講義.txt 0.4 版 │
│ │
│ ○ 軟體程式 : 以下軟體是以 Turbo C++ 3.0 開發。 │
│ old\DEBUG.DOC Debug 函式庫使用說明 │
│ old\DEBUG.CPP Debug 函式庫 C++ 原始碼 │
│ old\DEBUG.IPP Debug 函式庫 C++ 檔頭宣告 │
│ old\DEBUG.LIB Debug 函式庫 │
│ │
│ ○ 參考資料/相關資料: │
│ 1. Steve Maguire, "Writing solid code : Microsoft's techniques for │
│ developing bug free C programs", Microsoft Crop., 1993 │
│ 中譯本:「如何撰寫○錯誤程式」,旗標出版, 來源[自購] │
│ 2. 王豐緒(中譯), "Turbo C 參考手冊(下)",道明出版,1989,來源[自購] │
│ 3. Borland, tlib 線上說明 , 來源[軟體內附] │
│ 4. Borland, make 線上說明 , 來源[軟體內附] │
│ │
│ ○ 可作那些應用: │
│ 在建立函式庫時,可以參考本 IP 的說明,以建立出可供他人使用的函式庫, │
│ 包含函式庫的檔頭及說明文件的寫法在本 IP 中也有詳細的解說。 │
│ │
└─────────────────────────────────────┘

╔═════════════════════════════════════════╗
如何建立 C 語言函式庫_____________Jun.26,1995_________(c) 李棟樑 (Lee Dong-Liang)
如何建立 C/C++ 語言函式庫_________Aug.14,1997 ________(c) 李棟樑 (Lee Dong-Liang)
如何建立 C/C++ 語言函式庫_________Aug.19,1997 ________(c) 李棟樑 (Lee Dong-Liang)
如何建立 C/C++ 語言函式庫_________Feb.21,2000 ________(c) 李棟樑 (Lee Dong-Liang)
如何建立 C/C++ 語言函式庫_________May.09,2001 ________(c) 李棟樑 (Lee Dong-Liang)
╚═════════════════════════════════════════╝

╔═════════════════════════════════════════╗

■ 如何使用 Debug.Lib ── 一個除錯工具的函式庫
參考書目:"Writing solid code : Microsoft's techniques for developing
bug free C programs", Steve Maguire , 1993 Microsoft Crop.
中譯本:「如何撰寫○錯誤程式」,旗標出版

■ 如何將 Subroutine 轉成 Library 的型式

■ 使用 TLib 工具程式,造出 Library 檔
參考:線上的使用說明

■ 如何使用 Make 工具 ── C/C++ 語言提供的工具
參考:線上的使用說明

■ 提供 Library 用的 header 檔(include 檔)之寫法

■ 使用 Library 的方法

■ Library 說明檔的應有的內容

╚═════════════════════════════════════════╝

╔═════════════════════════════════════════╗

■ 如何使用 Debug.Lib ── 一個除錯工具的函式庫

/*---------------------------------------------------------------------------//
// Debug.DOC Debug 使用說明文件。 Date : Aug.14,1997 //
//---------------------------------------------------------------------------//
// 程式設計:李棟樑 Programmer : Lee Dong-Liang //
//---------------------------------------------------------------------------//
// Version 0.1 : Mar.22,1995 //
// Basis assert routine : ASSERTFILE(),ASSERT(),ASSERTMSG() for SDEBUG //
// //
// Version 0.2 : May.03,1995 //
// Added assert routine : ASSERTUSING() for DEBUG //
// //
// Version 0.3 : Aug.13,1997 //
// Added assert routine : ASSERTFUNCTION(),ASSERTFUNCUSING() for DEBUG //
// //
// #define SDEBUG is for Source Level debug Self-develop //
// #define DEBUG is for Application Level debug Group-develop //
// //
// SDEBUG mode include DEBUG mode. //
//---------------------------------------------------------------------------//
// Reference : "Writing solid code : Microsoft's techniques for developing //
// bug free C programs", Steve Maguire , 1993 Microsoft Crop. //
//---------------------------------------------------------------------------*/

■ Debug - C 語言除錯工具之說明

Debug C 語言除錯工具,主要是參考 "Writing solid code" 一書所寫成。
在書中說明了如何減少撰寫程式的錯誤,使錯誤能夠儘早發生。其中最有用的方法
有二:

(1)「匈牙利」(Hungarian)命名法

匈牙利命名法,是將冗長的英文名稱,取其適當的字母作為識別,如此只要看到
該識別字母或字串,就如同看到原來冗長的英文名稱。如:

void 取 v 字母代替,void 的變數名稱則加在 v 之後,例: void vName;
char 取 ch 或 c 代替,char 的變數名稱則加在 ch 或 c 之後,例:
char chName;
int 取 i 字母代替,int 的變數名稱則加在 i 之後,例: int iName;

如果是指標型的變數,則在變數名稱之前再加上 p ,表示 pointer。如:

void *pvName;
char *pchName;
int *piName;

利用這種方式,在寫程式時,可以立刻知道所使用的變數是什麼型態,
程式設計師可以在寫程式的同時,自行作變數型態的檢查,以減少錯誤。如:

long *plDailyTimer = (long *)0x0040006CL; // inc by INT 8 (18.2Hz)
long lDailyTimer;
lDailyTimer = *plDailyTimer; // * 與 p 互相抵消

在 C 中, * 表示是取指標位址所指到的值,而我們是用 p 來表示指標,
所以在第三行等號的右側的資料型態就為 long 。等號左側的資料型態亦為 long,
所以此行程式在語法上是正確的。同理,N維的指標變數就加N個 p 作為識別。

以下是 DEBUG.IPP 中所定義的變數型態:

typedef unsigned char byte;
typedef int flag;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef unsigned short word16;
typedef unsigned long word32;

以下是建議使用的變數名稱縮寫:

void vName; void *pvName;
char chName; char *pchName;
char *strName;
byte bName; byte *pbName;
int iName; int *piName;
flag fgName; flag *pfgName;
uint uiName; uint *puiName;
long lName; long *plName;
ulong ulName; ulong *pulName;
float fName; float *pfName;
double dName; double *pdName;
word16 w16Name; word16 *pw16Name;
word32 w32Name; word32 *pw32Name;


(2) 維護(assertion)敘述

當自己寫的函式庫要提供他人使用時,適當的利用維護敘述,可以建立安全的使用
介面,避免他人因為使用不當,而造成不可預期的後果。
C 語言有自己的維護函式- assert() ,使用方法如下:

assert(iTotalNumber < 1000);

當程式執行到該行時,若 iTotalNumber < 1000 則程式可以繼續執行;若
iTotalNumber >= 1000 ,則會秀出維護錯誤訊息的字串,並結束程式。
維護字串包含有:判斷式子、程式檔名及該行的行號。

// Test.CPP -- test assert message
#include <assert.h>
main()
{
int iTotalNumber=10000;
assert(iTotalNumber<1000);
}

C:\L\CPP\BIN>tt
Assertion failed: iTotalNumber<1000, file test.cpp, line 6
Abnormal program termination

assert() 是以巨集定義的函式(可參見 ASSERT.H),在每次呼叫時,就會將該判斷式
轉為字串,並將該檔的檔名及該行的行號加入維護訊息中。在同一個檔中使用多次
assert(),該檔的檔名字串就會多次出現在執行檔中,造成記憶體的浪費,
所以在 "Writing solid code" 書中,就使用另一種方式來解決此一問題。
詳細的說明,可參考該書的附錄。

■ Debug - 維護敘述使用說明

在原書中提供了 ASSERT(判斷式) 及 ASSERTMSG(判斷式,...) 兩個維護敘述指令。
當判斷式為零(False) 時,會秀出原始檔名及該行行號。

若以函式庫的方式供他人使用,在呼叫錯誤時,所秀出的檔名及行號並不是使用者
的資料,除非只用到一個函式,否則使用者也無法判斷是那一個函式呼叫錯誤。

筆者為了增強其功能,加上 ASSERTUSING(判斷式,函式名稱,...) 的維護敘述指令。
當判斷式為零(False) 時,會秀出使用函式的名稱及維護訊息,
方便程式設計師以函式庫的方式供他人使用。

ASSERT() 及 ASSERTMSG() 適合自己發展程式時使用,若希望維護敘述致能,
則在程式中加上 #define SDEBUG 或用 tcc -DSDEBUG 來編譯程式。

ASSERTUSING() 適合發展函式庫時使用,若希望維護敘述致能,
則在程式中加上 #define DEBUG 或用 tcc -DDEBUG 來編譯程式。
在 SDEBUG 模式下也可以使用 ASSERTUSING() 維護敘述指令。

如果在程式中沒有 #define DEBUG 或 #define SDEBUG,在編譯程式時也沒加
-DDEBUG 或 -DSDEBUG 的參數,則維護敘述就等於空指令 NULL; 不占任何空間。

以下是各維護敘述的說明:

□ ASSERTFILE(__FILE__)

用於宣告檔案名稱,以提供 ASSERT() 及 ASSERTMSG() 所使用。
此為原書所提供減少重覆儲存原始檔名的方法。
其中:__FILE__ 是 C/C++ 內定的系統變數,指的是目前檔案的全名。

□ ASSERT(flag fg)

如果 fg 為非零值(True),則執行空指令 (NULL),程式繼續執行。
如果 fg 為零(False),則在秀出該檔檔名及該行行號之後,程式結束。

□ ASSERTMSG(flag fg,char *strMsg)

如果 fg 為非零值(True),則執行空指令 (NULL),程式繼續執行。
如果 fg 為零(False),則在秀出該檔檔名、該行行號及維護訊息(strMsg)之後,
程式結束。

□ ASSERTUSING(flag fg,char *strFunctionName,char *strMsg)

如果 fg 為非零值(True),則執行空指令 (NULL),程式繼續執行。
如果 fg 為零(False),則在秀出函式名稱(strFunctionName)及維護訊息(strMsg)
之後,程式結束。

☆ ASSERTFUNCTION( "FunctionName" )

用於宣告函式名稱,以提供 ASSERTFUNCUSING() 使用。
此為 0.3 版減少重覆儲存函式名稱的方法。
其中:"FunctionName" 指的是目前函式的名稱。

☆ ASSERTFUNCUSING(flag fg,char *strMsg)

如果 fg 為非零值(True),則執行空指令 (NULL),程式繼續執行。
如果 fg 為零(False),則在秀出 ASSERTFUNCTION() 所指定的函式名稱
及維護訊息(strMsg) 之後,程式結束。

■ Debug 使用方法

□ #define SDEBUG 或 tcc -DSDEBUG ─ 自行發展程式

只要在程式一開始加上以下兩行:

#include "debug.ipp"
ASSERTFILE(__FILE__)

在須要維護的部分加上 ASSERT()、ASSERTMSG()
或 ASSERTUSING()、ASSERTFUNCTION()、ASSERTFUNCUSING()
即可。在編譯時,使用

tcc -ml -DSDEBUG filename debug.lib

則維護敘述及訊息都會在 filename.exe 中,以方便測試。
若確定在 filename.exe 中已無錯誤,則使用

tcc -ml filename

來編譯程式,如此所有的維護敘述及訊息都將會代換成 NULL; 指令,
不會佔有任何的空間。

□ #define DEBUG 或 tcc -DDEBUG ─ 發展函式庫

只在須要維護的部分加上 ASSERTUSING() 或
ASSERTFUNCTION()、ASSERTFUNCUSING() 即可。在編譯時,使用

tcc -ml -DDEBUG filename debug.lib

則維護敘述及訊息都會在 filename.exe 中,以方便測試。
若確定在 filename.exe 中已無錯誤,則使用

tcc -ml filename

■ Debug - 維護敘述使用技巧

維護敘述最主要的用途是檢查呼叫函式的引數。
因為大多數的錯誤是發生在呼叫函式,使用錯誤或不當的引數,其結果自然不正確。
利用維護敘述可以建立正確的呼叫介面,如此,只要在程式中有錯誤的呼叫函式,
在測試時,就可以提早發現錯誤。如:

// 回復硬體中斷向量
void Restore_IRQ(byte bIRQno,void interrupt far(*pBackup_irq)(...))
{
ASSERTFUNCTION("Restore_IRQ");
ASSERTFUNCUSING(bIRQno<=15,"IRQ# must between 0 - 15");
ASSERTFUNCUSING(pBackup_irq!=NULL,"Can not assign a NULL pointer to an ISR");

disable();
setvect(pbSoftINTofIRQ[bIRQno],pBackup_irq); /* Set IRQ routine */
enable();
}

在上面的例子中,可由維護敘述看出,硬體中斷的編號必須在 0 到 15 之間、
備份的中斷向量不可以是 NULL。
當使用大於 15 的硬體中斷編號,或備份的中斷向量是 NULL 時,
維護敘述就會發生作用了。

由於在沒有 #define DEBUG 時,維護敘述就等於是空指令,所以在設計維護敘述
時,必須考慮:把所有的維護敘述除去,也不會影響程式正常的執行。如:

// 將字串複製一份
// char *s1 = "This is a test string."
// char *s2 = strDup(s1);
char *strDup(char *str)
{
ASSERTUSING(str!=NULL,"strDup", // 正確的使用
"Duplicate string can't be NULL");

char *strNew=(char *)malloc(strlen(str)+1);
ASSERTUSING(strNew!=NULL,"strDup", // 不正確的使用
"Memory not enough");

strcpy(strNew,str);
return(strNew);
}

在上面的例子中,第一個維護敘述是正確的使用,以判斷要複製的字串是否為
NULL。第二個維護敘述則是不正確的使用。因為記憶體不足是屬於一般的程式錯誤,
在測試單一個函式時,不一定會發生。若將維護敘述除去,在執行時有可能發生
記憶體用完的狀況,到時候 strNew 會變為 NULL,再做 strcpy(strNew,str);
就會當機了。所以,不可以濫用維護敘述。

╚═════════════════════════════════════════╝

╔═════════════════════════════════════════╗

■ 如何將 Subroutine 轉成 Library 的型式

C 語言原始程式檔可組譯成 .obj 檔,C 將每一個 .obj 檔視為一個模組,
常用的模組可以結合成函式庫。在同一個函式庫中,可以有多個不同性質的模組,
但是同一個模組中,最好放性質相同的模組,因為 C 在連結時,即使只用到一個
函式,也會將整個模組連結到執行檔。 C 語言內建函式庫中的模組大部分是只有
一個副函式,在連結時才能產生較小的執行檔。

函式庫的副函式寫作技巧及注意事項:

(1) 模組之獨立性。要求一個副函式只負責執行某項單獨的功能,並且以簡單的
介面來和其它的模組溝通。最好的模組特性是內聚程度(Cohesion)高,
不同模組間的耦合程度(Coupling)低。

(2) 減少輸出訊息或沒有輸出訊息,以回傳值代表執行狀態。
在程式開發階段,最方便就是將數值用 printf() 印出,以判斷執行是否正常。
除非特別須要,一個函式庫的副函式應該不會有輸出訊息,因為您無法預測
使用者是在什麼狀況下使用該函式,所以最好是將輸出訊息全部取消。
取消的訊息可以用一個代碼表示,並以該碼為作回傳值,讓使用者知道函式
執行狀態。以測試是否在倚天中文系統為例:

#define TRUE 1
#define FALSE 0
flag IsETenChineseSystem(void)
{
asm{ mov ax,09100h
mov bx,0
int 10h
cmp bx,0
je NotInET };

// printf("倚天中文系統已載入。\n");
return TRUE;

NotInET:
// printf("ETen Chinese System doesn't found.\n");
return FALSE;
}

(3) 決定副函式中 變 與 不變 的部分。
當決定常用程式碼要寫成函式庫時,就要決定此段程式中那些是可變,
可變的部分就成為此函式的輸入引數。以 OneBeep() 為例

void OneBeep(void)
{
sound(110); // 頻率固定
delay(200); // 延遲固定
nosound();
}
void OneBeep(uint uiDelayTime)
{
sound(110); // 頻率固定
delay(uiDelayTime); // 延遲由輸入引數決定
nosound();
}
void OneBeep(uint uiFrequency,uint uiDelayTime)
{
sound(uiFrequency); // 頻率由輸入引數決定
delay(uiDelayTime); // 延遲由輸入引數決定
nosound();
}

(4) 簡化使用介面。以回復硬體中斷向量為例

void Restore_IRQ(int iSoftIntNo,void interrupt (*pBackup_irq)(...))
{
disable();
setvect(iSoftIntNo,pBackup_irq);
enable();
}

可在 header 檔中定義

#define IRQ0_INT 0x08 // timer (18.2 per second)
#define IRQ1_INT 0x09 // keyboard service required
#define IRQ2_INT 0x0A // slave 8259 or EGA/VGA vertical retrace
#define IRQ8_INT 0x70 // (AT) real time clock
#define IRQ9_INT 0x71 // (AT) software redirected to IRQ2
#define IRQ10_INT 0x72 // (AT) reserved
#define IRQ11_INT 0x73 // (AT) reserved
#define IRQ12_INT 0x74 // (AT) reserved
#define IRQ13_INT 0x75 // (AT) numeric coprocessor error
#define IRQ14_INT 0x76 // (AT) fixed disk controller
#define IRQ15_INT 0x77 // (AT) reserved
#define IRQ3_INT 0x0B // COM2 or COM4 service required
#define IRQ4_INT 0x0C // COM1 or COM3 service required
#define IRQ5_INT 0x0D // fixed disk or data request from LPT2
#define IRQ6_INT 0x0E // floppy disk service required
#define IRQ7_INT 0x0F // data request from LPT1

則使用者不必記 IRQ3 的軟體中斷向量編號為何,就可用

Restore_IRQ( IRQ3_INT, pBackup_irq); // 回復 IRQ3


☆ 另一個更簡便的方法是宣告一個整體變數並改程式碼如下:

byte pbSoftINTofIRQ[]={
IRQ0_INT, IRQ1_INT, IRQ2_INT, IRQ3_INT,
IRQ4_INT, IRQ5_INT, IRQ6_INT, IRQ7_INT,
IRQ8_INT, IRQ9_INT, IRQ10_INT, IRQ11_INT,
IRQ12_INT, IRQ13_INT, IRQ14_INT, IRQ15_INT };

void Restore_IRQ(byte bIRQno,void interrupt (*pBackup_irq)(...))
{
disable();
setvect(pbSoftINTofIRQ[bIRQno],pBackup_irq);
enable();
}

則使用者不必記您在 header 檔中對 IRQ3 軟體中斷向量編號的定義,就可用

Restore_IRQ( 3, pBackup_irq); // 回復 IRQ3

╚═════════════════════════════════════════╝

╔═════════════════════════════════════════╗

■ 使用 TLib 工具程式,造出 Library 檔

假設您寫的程式為 sub1.c 或 sub1.cpp ,要產生 .obj 檔的方式如下:

tcc -c sub1 ─→ 產生 small mode 的 sub1.obj
tcc -ml -c sub1 ─→ 產生 large mode 的 sub1.obj

在多種模式中常用的是 small 及 large mode ,筆者通常只有造出 large mode。

☆ 其中 -c 表示只作編譯(compile)不作連結(link),也就是只會產生 .obj 檔。
★ 要作成 Library 的 .c 或 .cpp 檔,不應該會有叫 main() 的函式。一個沒有
main() 函式的原始檔,執行 tcc 編譯時若不加上 -c 同樣會產生 .obj 檔,
但是 tcc 仍會試著幫你連結,產生 .exe 檔,只是這個 .exe 無法執行。


配合 Debug.Lib 時,可以使用

tcc -ml -c -DSDEBUG sub1 ─→ 產生 Self-develop 的 sub1.obj
tcc -ml -c -DDEBUG sub1 ─→ 產生 Group-develop 的 sub1.obj
tcc -ml -c sub1 ─→ 產生 不含維護敘述 的 sub1.obj

加上 -o 的參數可以指定 .obj 的檔名

tcc -ml -c -DSDEBUG -osub1s sub1 ─→ 產生 Self-develop 的 sub1s.obj
tcc -ml -c -DDEBUG -osub1g sub1 ─→ 產生 Group-develop 的 sub1g.obj
tcc -ml -c sub1 ─→ 產生 不含維護敘述 的 sub1.obj

.obj 檔產生後,可用 tlib 造出 library 檔:(假設要造出 lib1.lib)

tlib /c lib1 -+sub1, lib1
↑ ↑ ↑ ↑ ↑
│ │ │ │ └─ 造出 lib1.lst 的列表檔
│ │ │ └─── 要加入的 sub1.obj 模組
│ │ └───── 取代已經存在的模組
│ └──────── library 的檔名
└────────── 表示是 Case-Sensitive

如此, lib1.lib 就產生了。有關 tlib 的詳細用法可參考線上說明。

╚═════════════════════════════════════════╝

╔═════════════════════════════════════════╗

■ 如何使用 Make 工具 ── C 語言提供的工具

當一個函式庫擁有愈來愈多的模組時,make 可以簡化您更新函式庫的步驟。
當然您也可以把它用在編譯大型程式或是非程式資料檔。
make 能判斷資料檔是否更新,若有更新只要重新組譯並連結更新的部分即可。
makefile 的寫法如下:(詳細用法可參考線上說明)

○ 以 # 為開頭的資料 make 會視為註解。

○ 以下為定義基本格式

OutFile : InFile1 InFile2 \
InFile3 ...
Command1
Command2
...

當 InFile* 比 OutFile 「新」時,則執行 Command1, Command2 , ...
有多行資料時,可用 \ 續行。

○ 當 InFile* 也有定義一個類似的格式時

InFile1 : InFile11 InFile12 ...
Command11
...

則 make 做到 OutFile 時,會先判斷 InFile1* 是否比 InFile1 新,
若是,執行 Command11, ...
在依次判斷是否有定義 InFile2 , ... ,直到 InFile* 都做完,
再判斷 InFile* 是否比 OutFile 新,若是,執行 Command1, ...

○ 定義符號(Symbol)

Symbol1 = -ml

使用符號的方法: $(Symbol1)
如: tcc $(Symbol1) InFile1

符號也可以在執行 make 時定義: -D

make -DSymbol1=-ml

○ 以下是簡單的 makefile

##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-##
## LIB1.LIB Library 1 ##
## LIB1_D.LIB Library 1 with DEBUG information ##
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-##
## Programmer : Lee Dong-Liang ##
## Version : 1.0 Date : Jun.24,1995 ##
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-##
## Makefile for LIB1.LIB (for Large Mode only) ##
## Makefile for LIB1_D.LIB (for Large Mode only)(with Debug Info) ##
## Variable OPS General Options ##
## Variable OPS_D General Options with -DDEBUG ##
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-##

OPS =-c -C -ml
OPS_D=-c -C -ml -DDEBUG

lib1.lib : sub1.obj sub2.boj sub3.obj \
lib1_D.lib
tlib LIB1 ,LIB1

sub1.obj : sub1.cpp sub1.ipp
tcc $(OPS) -osub1.obj sub1.cpp
tlib /c LIB1 -+sub1.obj

sub2.obj : sub2.cpp sub2.ipp
tcc $(OPS) -osub2.obj sub2.cpp
tlib /c LIB1 -+sub2.obj

sub3.obj : sub3.cpp sub3.ipp
tcc $(OPS) -osub3.obj sub3.cpp
tlib /c LIB1 -+sub3.obj

lib1_D.lib : sub1_D.obj sub2_D.obj sub3_D.obj
tlib LIB1_D ,LIB1_D

sub1_D.obj : sub1.cpp sub1.ipp
tcc $(OPS_D) -osub1_D.obj sub1.cpp
tlib /c LIB1_D -+sub1_D.obj

sub2_D.obj : sub2.cpp sub2.ipp
tcc $(OPS_D) -osub2_D.obj sub2.cpp
tlib /c LIB1_D -+sub2_D.obj

sub3_D.obj : sub3.cpp sub3.ipp
tcc $(OPS_D) -osub3_D.obj sub3.cpp
tlib /c LIB1_D -+sub3_D.obj


○ make 內定是讀取檔名為 makefile 的資料檔,假設您的資料檔名為 lib1.mak
則用

make -flib1.mak

有關 make 詳細的用法,可參考線上說明。

╚═════════════════════════════════════════╝

╔═════════════════════════════════════════╗

■ 提供 Library 用的 header 檔(include 檔)之寫法

header 檔必須宣告您 library 中副函式的原型(prototype),供 C 在組譯時作
型別檢查。可將原始檔中定義函式的部分: 回傳值型態 副函式名稱(輸入引數)
拷貝到 header 檔,並在每一個宣告後加上分號 ; 。

▲若是物件函式庫,則要將整個 class 的定義拷貝到 header 檔,再將 class 中
以 inline function【註一】 方式定義函式的程式碼也拷貝到 header 檔。

▲若有定義常數 #define CONST1 value1 也可放入 header 檔。

▲若有整體性的變數可宣告 extern vartype GlobalVar1; 則使用者可直接存取該變數。

★★ 注意 ★★
如果在原始檔中也要 inlucde 同一個 header 檔,則要動一點手腳,否則會出現
重覆宣告的錯誤,因為

#include "lib1.h" /* 內已宣告 extern int iABC; */

int iABC; /* 在 lib1.c 中宣告 iABC 為整體變數 */

會讓 Compile 認為在 lib1.c 中, iABC 又是整體變數,又是外部整體變數,而無法
Compile。解決的方式是在 lib1.h 中對於整體性變數的宣告要用 #if ... #endif
包裝如下:

┌────────────────────────┐
│ │
│#ifndef __LIB1_NotIncludeExternal__ │
│ │
│ extern vartype GlobalVar1; │
│ extern vartype GlobalVar2; │
│ │
│#endif │
│ │
└────────────────────────┘

在 lib1.c 中,則要用以下的方式去 include lib1.h :

┌────────────────────────┐
│ │
│#define __LIB1_NotIncludeExternal__ │
│#include "lib1.h" │
│ │
└────────────────────────┘

如此,對於其他使用 lib1 的 .c / .cpp 在使用時,就如一般的 header 一樣:

┌────────────────────────┐
│ │
│#include "lib1.h" │
│ │
└────────────────────────┘

這樣,就可以讓原始的 lib1.c 及使用此函式庫的檔案共用一個 header 檔了。


▲若有自定的資料結構也要在 header 檔中宣告。

▲若 library 與組譯環境有關,則要加上判斷如:

#ifdef __cplusplus /* 在 C++ 的環境 __cplusplus 為真 */
extern "C" {
#endif

void Sub1(void); /* this subroutine is compiled by C */
...

#ifdef __cplusplus
}
#endif /* 以上是 C++ 呼叫 C 函式庫的標準 include 法 */

▲若與作業環境有關,也要加上判斷如:

#if defined( _Windows ) // 若使用者在 Windows 環境組譯,
#error This library does not support Windows. // 則秀出錯誤訊息後,
#endif // 中斷組譯。

▲在定義完以上資料後,可用以下定義將 header 檔包裝起來:

#ifndef __LIB1_H_ /* if not define __LIB1_H_ , do following */
#define __LIB1_H_ /* define __LIB1_H_ */

...... 之前定義好的 header 檔 ......

#endif /* end of #ifndef __LIB1_H_ */

這樣使用者在多次 #include<lib1.h> 時,可增快組譯速度。

如果 header 檔中定義的是一系列的函式,則可以在檔案前加上簡短的使用說明。
若有參考資料,也可列出。


【註一】何謂 inline function : 《 另一種 #define 》

○ 在 C++ 中,您可以在 class 中宣告同時定義 member function ,如下:

/* First example: Implicit inline statement */
int num; // global num
class cat {
public:
char* func(void) { return num; }
char* num;
}

此種 function 可稱為 inline function 。

○ 您也可以把宣告放在 class 中,在 class 外定義 inline function ,如下:

/* Second example: Explicit inline statement */
int num; // global num
class cat {
public:
char* func(void);
char* num;
}
inline char* cat::func(void) { return num; }

在 class 外定義 inline function ,就須在 function 前加上 inline 。

○ inline 除了可以定義 class 的 member function 外,也可以定義一般的函式:

inline int iMAX(int iA,int iB)
{
if( iA > iB ) return iA;
else return iB;
}

★ inline 就如同是 #define 一般,在編譯時會在有使用的地方將所定義的程式碼
作代換,省去 function call ,比較快;但是多次使用會造成程式碼重覆。
所以 inline function 比較適合用在程式碼小、在迴圈內使用,或是要以
空間換取時間的例子。

╚═════════════════════════════════════════╝

╔═════════════════════════════════════════╗

■ 使用 Library 的方法 ( 已完成 lib1.h 及 lib1.lib )

○ 在 .c 或 .cpp 的原始檔中:先 #include "lib1.h"
之後就可以使用 lib1.h 中定義的函式了。

// TEST.CPP Testing lib1 ...
#include "lib1.h"

main()
{
...
Sub1(); // sub-routine in lib1.lib
...
}

○ 編譯及連結

(1) 使用命令列: tcc

tcc -ml test lib1.lib
↑ ↑
└─────┴─ 兩者的模式要相同

(2) 使用整合環境: tc / bc

(a) 設定編譯模式

選 Options → Compiler → Code generation ...
將 Mode 的選項設定成 lib1.lib 的模式。
如:lib1.lib 是 Large 模式,就將 Mode 選項設定為 Large 。

(b) 新增 Project

選 Project → Open project ...
→ 自設一個新的 project 檔名 如: test.prj

(c) 加入 test.cpp , lib1.lib

再選 Project → Add item... ( 或是在 Project 視窗中按 [Insert] )
加入 test.cpp 及 lib1.lib

(d) 選 Compile → Build all

○ C/C++ 內定連結的函式庫

C/C++ 的函式庫 ( 在 ..\LIBRARY\ 目錄 ) 分為以下三類:

(1) 自動連結:包含大部分函式庫及執行檔檔頭,有

c0##MODE##.obj 其中 ##MODE## 為 t,s,m,c,l,h
c0f##MODE##.obj 分別為 Tiny , Small , Medium ,
c##MODE##.lib Compact , Large , Huge 。

(2) 選擇性連結:依使用者設定的環境及使用的狀況判斷是否連結,有

math##MODE##.lib
emu.lib
fp87.lib
overlay.lib
wildargs.obj

(3) 使用者自行連結:由使用者自己判斷是否連結,有

graphics.lib

▲ 利用 C/C++ 會自動連結 c##MODE##.lib 的特性,可以將自己的函式庫直接加到
c##MODE##.lib ,連結時就方便多了。如:

tlib /c cl.lib -+mod1, cl

☆ turboc.cfg 是 TC/TC++ 命令列環境設定檔,存在 ..\BIN\ 目錄下,內定的內容如下

-IC:\CPP\INCLUDE → 指定 include 檔的目錄
-LC:\CPP\LIB → 指定 library 檔的目錄

我們可以將自己的 include 及 library 目錄加在內定值之後:

-IC:\CPP\INCLUDE;d:\work\include → 指定 include 檔的目錄
-LC:\CPP\LIB;d:\work\lib → 指定 library 檔的目錄
-C → 支援 /* ... */ 巢狀註解

將自己寫的 include 檔存到 d:\work\include 、 library 檔存到 d:\work\lib
如此在 d:\work\newwork 下寫新程式 newwork.cpp 可用

#include <lib1.h> 或 #include "lib1.h"

引入 d:\work\include\lib1.h ,編譯連結時可用

tcc -ml newwork lib1.lib

連結 d:\work\lib\lib1.lib

╚═════════════════════════════════════════╝
╔═════════════════════════════════════════╗

■ Library 說明檔應有的內容

□ Library 簡介
□ 使用的資料結構
□ 整體變數一覽表
□ 巨集指令一覽表
□ 函數指令一覽表
□ 整體變數詳細說明
□ 巨集指令詳細說明
□ 函數指令詳細說明
□ 範例程式

□ Library 簡介
使用說明 / 參考資料 / 作者 / 版次 / 日期 / 改版作者 / 改版版次 / 改版日期

□ 使用的資料結構

typedef struct
{
int iNumb; /* Number of items */
int iLen; /* Length of each item */
char *strItem; /* End each item with NULL char */
/* and joining together */
} ItemType;


□ 整體變數一覽表
┌─────────┬───────────────┐
│ 變 數 名 稱 │ 功 能 簡 述 │
├─────────┼───────────────┤
│ GlobalVar1 │ 存 ..... │
│ ... │ ... │
└─────────┴───────────────┘

□ 巨集指令一覽表
□ 函數指令一覽表
┌─────────┬───────────────┐
│ 函 數 名 稱 │ 功 能 簡 述 │
├─────────┼───────────────┤
│ Sub_Routine1() │ 作 ..... │
│ ... │ ... │
└─────────┴───────────────┘

□ 整體變數詳細說明
─────────────────────────────────
變數名稱: GlobalVar1
功能: 存 ......
語法: vartype GlobalVar1;
載入檔: #include "lib1.h"
說明: ......
─────────────────────────────────

□ 巨集指令詳細說明
□ 函數指令詳細說明
─────────────────────────────────
函數名稱: Sub_Routine1()
功能: 作 ......
語法: vartype Sub_Routine1(vartype Arg1, vartype Arg2);
載入檔: #include "lib1.h"
呼叫範例: Sub_Routine1(1,2);
說明: 作 ......
傳回值: 0 為 ... 1 為 ...
註釋: // 注意事項,如不當的使用可能造成資料流失或當機。
範例: // 必須可組譯,若有特殊的組譯方法必須加以說明。
// 若此檔只說明一種特定的應用,可將使用範例放在最後。
─────────────────────────────────

□ 範例程式
必須可組譯,若有特殊的組譯方法必須加以說明。

╚═════════════════════════════════════════╝