perliol - Perl 在層中實作 IO 的 C API。
/* Defining a layer ... */
#include <perliol.h>
本文件說明當定義 USE_PERLIO
時,perlapio 中說明的 PerlIO 抽象行為與實作。
PerlIO 抽象在 perl5.003_02 中推出,但直到 perl5.7.0 才成為一個抽象概念。然而在那段期間,許多 perl 擴充套件都改用它,因此 API 大多固定以維持(原始碼)相容性。
實作的目標是以彈性且與平台無關的方式提供 PerlIO API。這也是「物件導向 C,加上虛擬函式表」方法的嘗試,可套用於 Raku。
PerlIO 是一組層級堆疊。
堆疊的低層級與低層級作業系統呼叫(C 中的檔案描述子)一起運作,將位元組輸入和輸出,堆疊的高層級則會緩衝、篩選並以其他方式處理 I/O,並將字元(或位元組)傳回 Perl。術語上方和下方用於指稱堆疊層級的相對位置。
一層包含「vtable」,即 I/O 作業的表格(在 C 層級中,是函式指標的表格)和狀態旗標。vtable 中的函式實作「開啟」、「讀取」和「寫入」等作業。
當要求 I/O,例如「讀取」時,要求會從 Perl 透過各層級的「讀取」函式向下傳遞堆疊,然後在底部從作業系統服務要求輸入,接著結果會傳回堆疊,最後被詮釋為 Perl 資料。
要求不一定總是會一路傳遞到作業系統:這就是 PerlIO 緩衝發揮作用的地方。
當您執行 open() 並指定要部署的額外 PerlIO 層級時,您指定的層級會「推入」到既有預設堆疊的頂端。一種看待方式是「作業系統在左側」而「Perl 在右側」。
這個預設堆疊中有哪些確切的層級取決於許多因素:您的作業系統、Perl 版本、Perl 編譯時間組態和 Perl 執行時間組態。請參閱 PerlIO、perlrun 中的「PERLIO」 和 open 以取得更多資訊。
binmode() 的運作方式類似於 open():預設情況下,指定的層級會推入到既有堆疊的頂端。
不過,請注意,即使指定的層級對於 open() 和 binmode() 而言是「推入頂端」,這並不表示其影響僅限於「頂端」:PerlIO 層級可以非常「活躍」,並檢查和影響堆疊中更深層的層級。例如,有一個稱為「raw」的層級,會重複「彈出」層級,直到它到達第一個宣告自己有能力處理二進位資料的層級。這些「推入」的層級會依序從左到右處理。
sysopen()(不出所料)在堆疊中運作的層級比 open() 低。例如,在 Unix 或類 Unix 系統中,sysopen() 直接在檔案描述子的層級運作:就 PerlIO 層級而言,它只使用「unix」層級,而這是一個相當薄的包裝器,包覆在 Unix 檔案描述子之上。
最初討論修改 IO 串流行為的能力時,使用術語「守則」來表示新增的實體。這(我相信)來自於在「sfio」中使用這個術語,而「sfio」又是從 Unix 終端的「線路守則」借用而來。不過,這份文件(和 C 程式碼)使用術語「層級」。
我希望這是個自然而然的術語,因為它符合實作,而且應該避免「守則」一詞在較早用於性質相當不同的東西時所固有的涵義。
基本的資料結構是 PerlIOl
typedef struct _PerlIO PerlIOl;
typedef struct _PerlIO_funcs PerlIO_funcs;
typedef PerlIOl *PerlIO;
struct _PerlIO
{
PerlIOl * next; /* Lower layer */
PerlIO_funcs * tab; /* Functions for this layer */
U32 flags; /* Various flags for state */
};
PerlIOl *
是結構的指標,而應用程式層級的 PerlIO *
是 PerlIOl *
的指標,也就是結構指標的指標。這讓應用程式層級的 PerlIO *
可以保持不變,而底層的實際 PerlIOl *
卻可以改變。(比較 Perl 的 SV *
,它在標量類型改變時,其 sv_any
欄位會改變,但它本身保持不變。)因此,IO 串流通常表示為指向這個「層級」連結串列的指標。
請注意,由於 PerlIO *
中有雙重間接參照,因此 &(perlio->next)
「是」PerlIO *
,所以至少在某種程度上,一層可以使用下層的「標準」API。
「層級」由兩個部分組成
「層級類別」的函式和屬性。
特定控制項的每個執行個體資料。
函式和屬性是透過 PerlIOl
的「標籤」(用於表格)成員存取的。函式(層級「類別」的方法)是固定的,並由 PerlIO_funcs
類型定義。它們大致與公開的 PerlIO_xxxxx
函式相同
struct _PerlIO_funcs
{
Size_t fsize;
char * name;
Size_t size;
IV kind;
IV (*Pushed)(pTHX_ PerlIO *f,
const char *mode,
SV *arg,
PerlIO_funcs *tab);
IV (*Popped)(pTHX_ PerlIO *f);
PerlIO * (*Open)(pTHX_ PerlIO_funcs *tab,
PerlIO_list_t *layers, IV n,
const char *mode,
int fd, int imode, int perm,
PerlIO *old,
int narg, SV **args);
IV (*Binmode)(pTHX_ PerlIO *f);
SV * (*Getarg)(pTHX_ PerlIO *f, CLONE_PARAMS *param, int flags)
IV (*Fileno)(pTHX_ PerlIO *f);
PerlIO * (*Dup)(pTHX_ PerlIO *f,
PerlIO *o,
CLONE_PARAMS *param,
int flags)
/* Unix-like functions - cf sfio line disciplines */
SSize_t (*Read)(pTHX_ PerlIO *f, void *vbuf, Size_t count);
SSize_t (*Unread)(pTHX_ PerlIO *f, const void *vbuf, Size_t count);
SSize_t (*Write)(pTHX_ PerlIO *f, const void *vbuf, Size_t count);
IV (*Seek)(pTHX_ PerlIO *f, Off_t offset, int whence);
Off_t (*Tell)(pTHX_ PerlIO *f);
IV (*Close)(pTHX_ PerlIO *f);
/* Stdio-like buffered IO functions */
IV (*Flush)(pTHX_ PerlIO *f);
IV (*Fill)(pTHX_ PerlIO *f);
IV (*Eof)(pTHX_ PerlIO *f);
IV (*Error)(pTHX_ PerlIO *f);
void (*Clearerr)(pTHX_ PerlIO *f);
void (*Setlinebuf)(pTHX_ PerlIO *f);
/* Perl's snooping functions */
STDCHAR * (*Get_base)(pTHX_ PerlIO *f);
Size_t (*Get_bufsiz)(pTHX_ PerlIO *f);
STDCHAR * (*Get_ptr)(pTHX_ PerlIO *f);
SSize_t (*Get_cnt)(pTHX_ PerlIO *f);
void (*Set_ptrcnt)(pTHX_ PerlIO *f,STDCHAR *ptr,SSize_t cnt);
};
結構的前幾個成員提供函式表格大小,用於相容性檢查層級的「名稱」、每個執行個體資料的 malloc
大小,以及一些作為整個類別屬性的旗標(例如它是否為緩衝層級),然後是屬於四個基本群組的函式
開啟和設定函式
基本 IO 作業
Stdio 類別緩衝選項。
支援 Perl 傳統「快速」存取緩衝區的函式。
層級不必實作所有函式,但整個表格必須存在。未實作的插槽可以是 NULL(呼叫時會導致錯誤),或可以填入程式碼片段,以「繼承」來自「基底類別」的行為。這種「繼承」對層級的所有執行個體都是固定的,但由於層級會選擇哪些程式碼片段來填入表格,因此可以進行有限的「多重繼承」。
每個執行個體的資料都保存在 PerlIOl 結構之外的記憶體中,方法是將 PerlIOl 設為層結構的第一個成員,如下所示
typedef struct
{
struct _PerlIO base; /* Base "class" info */
STDCHAR * buf; /* Start of buffer */
STDCHAR * end; /* End of valid part of buffer */
STDCHAR * ptr; /* Current position in buffer */
Off_t posn; /* Offset of buf into the file */
Size_t bufsiz; /* Real size of buffer */
IV oneword; /* Emergency buffer */
} PerlIOBuf;
這樣一來(就像 Perl 的標量一樣),指向 PerlIOBuf 的指標就可以視為指向 PerlIOl 的指標。
table perlio unix
| |
+-----------+ +----------+ +--------+
PerlIO ->| |--->| next |--->| NULL |
+-----------+ +----------+ +--------+
| | | buffer | | fd |
+-----------+ | | +--------+
| | +----------+
以上嘗試說明層架構在簡單情況下的運作方式。應用程式的 PerlIO *
指向表示開啟(已配置)句柄的表格中的某個項目。例如,表格中的前三個插槽分別對應到 stdin
、stdout
和 stderr
。表格反過來指向句柄的目前「頂層」層 - 在這種情況下是通用緩衝層「perlio」的一個執行個體。該層進一步指向下一層 - 在這種情況下是低階「unix」層。
以上大致等同於「stdio」緩衝串流,但具有更大的彈性
如果 Unix 層級的 read
/write
/lseek
不適用於(例如)socket,則可以在開啟時(甚至動態地)用「socket」層取代「unix」層。
不同的句柄可以有不同的緩衝機制。如果使用 mmap
讀取磁碟檔案比使用 read
快,則「頂層」層可以是「mmap」層。可以透過不使用緩衝層來實作「未緩衝」串流。
可以在資料流動時插入額外的層來處理資料。這是將架構納入 Perl 5.7.0+ 的主要原因 - 我們需要一種機制,讓資料可以在 Perl 的內部編碼(至少在概念上是 UTF-8 的 Unicode)和系統使用的「原生」格式之間進行轉換。這由「:encoding(xxxx)」層提供,該層通常位於緩衝層之上。
可以新增一個執行「\n」到 CRLF 轉換的層。這個層可以在任何平台上使用,而不僅僅是通常執行此類轉換的平台。
通用旗標位元是從傳遞給 PerlIO_open()
的模式字串推論出來的 O_XXXXX
樣式旗標和典型緩衝層狀態位元的混合體。
檔案結束。
允許寫入,即以「w」、「r+」或「a」等方式開啟。
允許讀取,即以「r」或「w+」(甚至「a+」 - 噁)方式開啟。
發生錯誤(對於 PerlIO_error()
)。
由開啟模式建議的截斷檔案。
所有寫入都應為附加。
層正在執行 Win32 類型的「\n」對應至輸出 CR、LF,以及 CR、LF 對應至輸入「\n」。通常提供的「crlf」層是唯一需要擔心這一點的層。如果層類別設定了 PERLIO_K_CANCRLF
位元,PerlIO_binmode()
會處理這個旗標,而不是新增/移除層。
寫入此層的資料應編碼為 UTF-8;此層提供的資料應視為編碼為 UTF-8。可由「:utf8」虛擬層在任何層上設定。也會在「:encoding」層上設定。
層未緩衝 - 亦即寫入此層時,應寫入至下一層。
此層的緩衝區目前保留寫入其中但未傳送至下一層的資料。
此層的緩衝區目前保留從下一層讀取的未消耗資料。
層是行緩衝。每當看到「\n」時,應將寫入資料傳遞至下一層。然後應處理「\n」之後的任何資料。
檔案已 unlink()
,或應在 close()
時刪除。
已開啟處理程序。
此層的這個實例支援「快速 gets
」介面。通常根據類別的 PERLIO_K_FASTGETS
和表格中函數的存在來設定。不過,通常提供該介面的類別可能需要在特定實例中避免它。「待處理」層在推送到不支援介面的層上方時需要這樣做。(Perl 的 sv_gets()
不希望串流的快速 gets
行為在一次「取得」期間改變。)
Size_t fsize;
函數表格的大小。這會與 PerlIO 程式碼「知道」的值進行比較,作為相容性檢查。未來版本可能能夠容忍針對舊版標頭編譯的層。
char * name;
Perl 在 open() 中應該呼叫其 open() 方法的層的名稱。例如,如果層稱為 APR,您將呼叫
open $fh, ">:APR", ...
Perl 知道它必須呼叫 APR 層實作的 PerlIOAPR_open() 方法。
Size_t size;
每個實例資料結構的大小,例如
sizeof(PerlIOAPR)
如果這個欄位為零,則 PerlIO_pushed
不會配置任何記憶體,並假設層的 Pushed 函數會執行任何必要的層堆疊處理 - 用於避免虛擬層的配置/釋放開銷。如果欄位是非零,則它必須至少為 PerlIOl
的大小,PerlIO_pushed
會為層的資料結構配置記憶體,並將新層連結到串流的堆疊上。(如果層的 Pushed 方法傳回錯誤指示,則層會再次彈出。)
IV kind;
PERLIO_K_BUFFERED
層已快取。
PERLIO_K_RAW
層可接受在 binmode(FH) 堆疊中 - 也就是說,它不會(或將自行設定為不會)轉換通過它的位元組。
PERLIO_K_CANCRLF
層可以在「\n」和 CRLF 行尾之間進行轉換。
PERLIO_K_FASTGETS
層允許緩衝區偵測。
PERLIO_K_MULTIARG
在層的 open() 接受比平常更多的引數時使用。額外的引數不應出現在 MODE
引數之前。使用這個旗標時,由層來驗證引數。
IV (*Pushed)(pTHX_ PerlIO *f,const char *mode, SV *arg);
唯一絕對必要的函數。在層推送到堆疊上時呼叫。如果這是發生在開啟後,mode
引數可能是 NULL。如果傳遞了引數字串,arg
將是非 NULL
。在大部分情況下,這應該呼叫 PerlIOBase_pushed()
,以將 mode
轉換為適當的 PERLIO_F_XXXXX
旗標,以及層本身執行的任何動作。如果層不希望引數,它既不需要儲存傳遞給它的引數,也不提供 Getarg()
(它可能 Perl_warn
引數是未預期的)。
成功時傳回 0。失敗時傳回 -1,並應設定 errno。
IV (*Popped)(pTHX_ PerlIO *f);
當層從堆疊中彈出時呼叫。通常在呼叫 Close()
之後,層會彈出。但是,如果程式動態管理串流上的層,則可以在不關閉層的情況下彈出層。在這種情況下,Popped()
應該釋放任何未直接保留在層的結構中的資源(緩衝區、轉換表,...)。它還應該將從下層讀取並緩衝的任何未使用的資料 Unread()
回到該層,以便可以重新提供給現在位於上方任何位置。
成功和失敗時傳回 0。如果 Popped()
傳回 true,則 perlio.c 假設層已自行彈出,或層非常特殊,需要出於其他原因保留。在大多數情況下,它應該傳回 false。
PerlIO * (*Open)(...);
Open()
方法有許多引數,因為它結合了 perl 的 open
、PerlIO_open
、perl 的 sysopen
、PerlIO_fdopen
和 PerlIO_reopen
的功能。完整的原型如下
PerlIO * (*Open)(pTHX_ PerlIO_funcs *tab,
PerlIO_list_t *layers, IV n,
const char *mode,
int fd, int imode, int perm,
PerlIO *old,
int narg, SV **args);
Open 應該(也許間接地)呼叫 PerlIO_allocate()
來配置表格中的槽,並透過呼叫 PerlIO_push
將其與開啟檔案的層資訊關聯。layers 是傳遞給 PerlIO *
的所有層的陣列,以及傳遞給它們的任何引數,n 是呼叫的層的該陣列中的索引。巨集 PerlIOArg
將傳回傳遞給層的(可能是 NULL
)SV *。
當層開啟或取得檔案描述子的擁有權時,該層負責將檔案描述子的 close-on-exec 旗標設定為正確狀態。對於小於或等於 PL_maxsysfd
的檔案描述子,該旗標應清除,而對於編號較高的任何檔案描述子,則應設定。為了執行緒安全性,當層開啟新的檔案描述子時,如果可能,它應該在最初設定 close-on-exec 旗標的情況下開啟它。
mode 字串是「fopen()
類似」的字串,它會符合正規表示式 /^[I#]?[rwa]\+?[bt]?$/
。
'I'
前置詞會在透過特殊 PerlIO_fdopen
呼叫建立 stdin
..stderr
時使用;'#'
前置詞表示這是 sysopen
,且應將 imode 和 perm 傳遞給 PerlLIO_open3
;'r'
表示read(讀取),'w'
表示write(寫入),'a'
表示append(附加)。'+'
後置詞表示允許讀取和寫入/附加。'b'
後置詞表示檔案應該是二進位的,而 't'
表示它是文字的。(幾乎所有層都應該以二進位模式執行 IO,並忽略 b/t 位元。應加入 :crlf
層來處理區別。)
如果 old 不是 NULL
,則這是 PerlIO_reopen
。Perl 本身並未使用此功能(目前?),且語意有點模糊。
如果 fd 非負值,則它是數字檔案描述符 fd,它將以與提供的模式字串相容的方式開啟,因此呼叫等同於 PerlIO_fdopen
。在此情況下,nargs 將為零。檔案描述符可以設定或清除 close-on-exec 旗標;由取得其擁有權的層負責將旗標設定為正確的狀態。
如果 nargs 大於零,則它會提供傳遞給 open
的引數數量,否則如果呼叫 PerlIO_open
,則它會為 1。在簡單的情況下,SvPV_nolen(*args) 是要開啟的路徑名稱。
如果一層提供 Open()
,它通常應該呼叫下一層 (如果有) 的 Open()
方法,然後在成功時將其推送到最上方。PerlIOBase_open
提供的功能正是如此,因此在大部分情況下,您不必撰寫自己的 Open()
方法。如果未定義此方法,其他層在開啟期間可能會難以將其推送到最上方。
如果執行 PerlIO_push
且開啟失敗,則它必須 PerlIO_pop
本身,因為如果不這樣做,則不會移除該層,可能會導致嚴重的問題。
失敗時傳回 NULL
。
IV (*Binmode)(pTHX_ PerlIO *f);
選用。在推入 :raw
層時使用 (明確或因為 binmode(FH) 的結果)。如果不存在,則會彈出該層。如果存在,則應將該層設定為二進位 (或彈出自身) 並傳回 0。如果傳回 -1 表示錯誤,則 binmode
會失敗,且該層仍會保留在堆疊中。
SV * (*Getarg)(pTHX_ PerlIO *f,
CLONE_PARAMS *param, int flags);
選用。如果存在,則應傳回一個 SV *,代表推入該層時傳遞給該層的字串引數。例如,":encoding(ascii)" 會傳回一個 SvPV,其值為 "ascii"。(在大部分情況下,可以忽略 param 和 flags 引數)
Dup
使用 Getarg
擷取最初傳遞給 Pushed
的引數,因此如果您的層對 Pushed
有額外的引數,且會被 Dup
,則您必須實作此函式。
IV (*Fileno)(pTHX_ PerlIO *f);
傳回處理序的 Unix/Posix 數字檔案描述符。通常 PerlIOBase_fileno()
(它只會詢問下一層) 就足以達成此目的。
傳回 -1 表示錯誤,這表示包含了圖層無法提供此類檔案描述子的情況。
PerlIO * (*Dup)(pTHX_ PerlIO *f, PerlIO *o,
CLONE_PARAMS *param, int flags);
XXX:需要更多文件。
當執行緒產生時,用於「複製」程序的一部分(這種情況下,param 將為非 NULL),以及當串流透過「&」在 open
中複製時。
類似於 Open
,在成功時傳回 PerlIO*,在失敗時傳回 NULL
。
SSize_t (*Read)(pTHX_ PerlIO *f, void *vbuf, Size_t count);
基本讀取操作。
通常會呼叫 Fill
並操作指標(可能透過 API)。PerlIOBuf_read()
可能適用於提供「快速取得」方法的衍生類別。
傳回實際讀取的位元組,或在發生錯誤時傳回 -1。
SSize_t (*Unread)(pTHX_ PerlIO *f,
const void *vbuf, Size_t count);
stdio 的 ungetc()
的超集。應安排未來的讀取來查看 vbuf
中的位元組。如果沒有明顯更好的實作,則 PerlIOBase_unread()
會透過在呼叫圖層上方推入「假的」的「待處理」圖層來提供函數。
傳回未讀取字元的數量。
SSize_t (*Write)(PerlIO *f, const void *vbuf, Size_t count);
基本寫入操作。
傳回寫入的位元組,或在發生錯誤時傳回 -1。
IV (*Seek)(pTHX_ PerlIO *f, Off_t offset, int whence);
定位檔案指標。通常應呼叫其自己的 Flush
方法,然後呼叫下一層的 Seek
方法。
在成功時傳回 0,在失敗時傳回 -1。
Off_t (*Tell)(pTHX_ PerlIO *f);
傳回檔案指標。可能基於圖層快取的位置概念,以避免開銷。
在無法取得檔案指標時傳回 -1。
IV (*Close)(pTHX_ PerlIO *f);
關閉串流。通常應呼叫 PerlIOBase_close()
來清除自身並關閉下方的圖層,然後取消配置任何未直接保存在資料結構中的資料結構(緩衝區、轉換表,...)。
在成功時傳回 0,在失敗時傳回 -1。
IV (*Flush)(pTHX_ PerlIO *f);
應使串流狀態與下方的圖層一致。也就是說,任何緩衝的寫入資料都應寫入,並調整下層檔案的位置以符合從下方讀取但實際上未使用的資料。(或許應將此類資料 Unread()
到下層。)
在成功時傳回 0,在失敗時傳回 -1。
IV (*Fill)(pTHX_ PerlIO *f);
應從下方的圖層填滿此圖層的緩衝區(用於讀取)。當您「建立 PerlIOBuf 圖層的子類別」時,您想要使用其 _read 方法,並提供您自己的填滿方法,以填滿 PerlIOBuf 的緩衝區。
在成功時傳回 0,在失敗時傳回 -1。
IV (*Eof)(pTHX_ PerlIO *f);
傳回檔案結束指標。PerlIOBase_eof()
通常就足夠了。
在檔案結束時傳回 0,在檔案未結束時傳回 1,在發生錯誤時傳回 -1。
IV (*Error)(pTHX_ PerlIO *f);
傳回錯誤指標。通常 PerlIOBase_error()
就足夠了。
如果有錯誤傳回 1(通常是設定 PERLIO_F_ERROR
時),否則傳回 0。
void (*Clearerr)(pTHX_ PerlIO *f);
清除檔案結尾和錯誤指標。應該呼叫 PerlIOBase_clearerr()
來設定 PERLIO_F_XXXXX
旗標,這樣就夠了。
void (*Setlinebuf)(pTHX_ PerlIO *f);
將串流標記為行緩衝。PerlIOBase_setlinebuf()
會設定 PERLIO_F_LINEBUF 旗標,通常就足夠了。
STDCHAR * (*Get_base)(pTHX_ PerlIO *f);
配置(如果尚未配置)此層的讀取緩衝區,並傳回指標。失敗時傳回 NULL。
Size_t (*Get_bufsiz)(pTHX_ PerlIO *f);
傳回上一次 Fill()
放入緩衝區的位元組數。
STDCHAR * (*Get_ptr)(pTHX_ PerlIO *f);
傳回此層緩衝區中目前的讀取指標。
SSize_t (*Get_cnt)(pTHX_ PerlIO *f);
傳回目前緩衝區中剩餘的讀取位元組數。
void (*Set_ptrcnt)(pTHX_ PerlIO *f,
STDCHAR *ptr, SSize_t cnt);
調整讀取指標和位元組計數,以符合 ptr
和/或 cnt
。應用程式(或上層)必須確保它們是一致的。(偏執狂可以檢查。)
若要詢問下層,請使用 PerlIONext(PerlIO *f)。
若要檢查 PerlIO* 是否有效,請使用 PerlIOValid(PerlIO *f)。(這其實只是檢查指標是否非 NULL,以及該指標後面的指標是否非 NULL。)
PerlIOBase(PerlIO *f) 傳回「基礎」指標,換句話說,就是 PerlIOl*
指標。
PerlIOSelf(PerlIO* f, type) 傳回轉換為 type 的 PerlIOBase。
Perl_PerlIO_or_Base(PerlIO* f, callback, base, failure, args) 會呼叫層 f 的函式中的 callback (僅使用 IO 函式的名稱,例如「Read」) 搭配 args,或者如果沒有此類 callback,則會呼叫 callback 的 base 版本搭配相同的 args,或者如果 f 無效,則將 errno 設定為 EBADF 並傳回 failure。
Perl_PerlIO_or_fail(PerlIO* f, callback, failure, args) 會呼叫層 f 的函式中的 callback 搭配 args,或者如果沒有此類 callback,則將 errno 設定為 EINVAL。或者如果 f 無效,則將 errno 設定為 EBADF 並傳回 failure。
Perl_PerlIO_or_Base_void(PerlIO* f, callback, base, args) 會呼叫層 f 的函式中的 callback 搭配 args,或者如果沒有此類 callback,則會呼叫 callback 的 base 版本搭配相同的 args,或者如果 f 無效,則將 errno 設定為 EBADF。
Perl_PerlIO_or_fail_void(PerlIO* f, callback, args) 會呼叫層 f 的函式中的 callback 搭配 args,或者如果沒有此類 callback,則將 errno 設定為 EINVAL。或者如果 f 無效,則將 errno 設定為 EBADF。
如果您發現實作文件不清楚或不足,請查看現有的 PerlIO 層實作,其中包括
C 實作
Perl 核心中的 perlio.c 和 perliol.h 實作「unix」、「perlio」、「stdio」、「crlf」、「utf8」、「byte」、「raw」、「pending」層,以及適用的「mmap」和「win32」層。(「win32」目前尚未完成且未使用,若要查看 Win32 中改用什麼,請參閱 "PerlIO 中的「查詢檔案處理的層」" )。
Perl 核心中的 PerlIO::encoding、PerlIO::scalar、PerlIO::via。
CPAN 上的 PerlIO::gzip 和 APR::PerlIO (mod_perl 2.0)。
Perl 實作
Perl 核心中的 PerlIO::via::QuotedPrint 和 CPAN 上的 PerlIO::via::*。
如果您正在建立 PerlIO 層,您可能想要偷懶,換句話說,僅實作您有興趣的方法。您可以將其他方法替換為「空白」方法
PerlIOBase_noop_ok
PerlIOBase_noop_fail
(什麼都不做,分別傳回零和 -1),或者對於某些方法,您可以使用 NULL 方法假設預設行為。Open 方法會在「父層」中尋找協助。下表摘要說明了行為
method behaviour with NULL
Clearerr PerlIOBase_clearerr
Close PerlIOBase_close
Dup PerlIOBase_dup
Eof PerlIOBase_eof
Error PerlIOBase_error
Fileno PerlIOBase_fileno
Fill FAILURE
Flush SUCCESS
Getarg SUCCESS
Get_base FAILURE
Get_bufsiz FAILURE
Get_cnt FAILURE
Get_ptr FAILURE
Open INHERITED
Popped SUCCESS
Pushed SUCCESS
Read PerlIOBase_read
Seek FAILURE
Set_cnt FAILURE
Set_ptrcnt FAILURE
Setlinebuf PerlIOBase_setlinebuf
Tell FAILURE
Unread PerlIOBase_unread
Write FAILURE
FAILURE Set errno (to EINVAL in Unixish, to LIB$_INVARG in VMS)
and return -1 (for numeric return values) or NULL (for
pointers)
INHERITED Inherited from the layer below
SUCCESS Return 0 (for numeric return values) or a pointer
檔案 perlio.c
提供下列層
一個基本非緩衝層,它呼叫 Unix/POSIX read()
、write()
、lseek()
、close()
。沒有緩衝。即使在區分 O_TEXT 和 O_BINARY 的平台上,此層始終為 O_BINARY。
一個非常完整的通用緩衝層,提供整個 PerlIO API。它也打算用作其他層的「基礎類別」。(例如,它的 Read()
方法是以 Get_cnt()
/Get_ptr()
/Set_ptrcnt()
方法實作的)。
"perlio" 在 "unix" 上提供一個完整的 stdio 替換,透過 PerlIO API 可見。當系統的 stdio 不允許 perl 的「快速取得」存取,且不區分 O_TEXT
和 O_BINARY
時,這是 USE_PERLIO 的預設值。
一個透過層架構提供 PerlIO API 的層,但透過呼叫系統的 stdio 來實作它。如果系統的 stdio 提供足夠的存取權,允許 perl 的「快速取得」存取,且不區分 O_TEXT
和 O_BINARY
,則這是(目前)的預設值。
一個使用 "perlio" 作為基礎類別的衍生層。它提供 Win32 類型的「\n」至 CR,LF 轉換。可以套用在 "perlio" 上方,或作為緩衝層本身。如果系統區分 O_TEXT
和 O_BINARY
開啟,則 "crlf" 在 "unix" 上是預設值。(在某個時間點,「unix」將在該平台上被「原生」Win32 IO 層取代,因為 Win32 的讀寫層有各種缺點。)「crlf」層是一個以某種方式轉換資料的層的合理模型。
如果 Configure 偵測到 mmap()
函式,則提供此層(以 "perlio" 為「基礎」),它透過 mmap() 檔案來執行「讀取」操作。在現代系統上,效能提升有限,因此它主要作為概念證明而存在。它很可能會在某個時間點從核心解除捆綁。「mmap」層是一個極簡主義「衍生」層的合理模型。
"perlio" 的「內部」衍生,可用於為沒有緩衝或無法處理的層提供 Unread() 函式。(基本上,此層的 Fill()
會將自己從堆疊中彈出,然後從下方的層繼續讀取。)
一個從不存在於層堆疊中的虛擬層。相反地,當「推入」時,它實際上會彈出堆疊並移除自己,然後呼叫堆疊中所有層的 Binmode 函式表條目 - 通常這(透過 PerlIOBase_binmode)會移除任何未設定 PERLIO_K_RAW
位元的層。層可以透過定義自己的 Binmode 條目來修改該行為。
另一個虛擬層。當被推入時,它會彈出自己並在堆疊頂端的層(現在再次成為頂端層)上設定 PERLIO_F_UTF8
旗標。
此外,perlio.c 也提供許多 PerlIOBase_xxxx()
函式,這些函式旨在用於不需要為特定方法執行任何特殊動作的類別的表格槽中。
擴充套件模組可以提供層。當遇到未知層時,PerlIO 程式碼會執行等同於
use PerlIO 'layer';
其中的 layer 是未知層。PerlIO.pm 然後會嘗試
require PerlIO::layer;
如果在該程序之後層仍然未定義,則 open
會失敗。
以下擴充套件層與 perl 捆綁在一起
use Encoding;
讓此層可用,儘管 PerlIO.pm 「知道」在哪裡可以找到它。它是作為一個層的範例,它在被呼叫時會接收一個引數,如下所示
open( $fh, "<:encoding(iso-8859-7)", $pathname );
提供從純量讀取資料和寫入資料的支援。
open( $fh, "+<:scalar", \$scalar );
當一個控制代碼如此開啟時,讀取會從 $scalar 的字串值取得位元組,而寫入會變更值。在這兩種情況下,$scalar 中的位置都從 0 開始,但可以透過 seek
變更,並透過 tell
判斷。
請注意,當如此呼叫 open() 時,會暗示此層
open( $fh, "+<", \$scalar );
提供讓層可以作為 Perl 程式碼實作。例如
use PerlIO::via::StripHTML;
open( my $fh, "<:via(StripHTML)", "index.html" );
請參閱 PerlIO::via 以取得詳細資料。
需要執行的動作,以改善此文件。
說明如何建立有效的 fh,而不需要透過 open()(即套用一個層)。例如,如果檔案不是透過 perl 開啟,但我們想要取得一個 fh,就像它是由 Perl 開啟的一樣。
PerlIO_apply_layera 如何配合,它的文件在哪裡,它是否已公開?
目前範例可以類似如下
PerlIO *foo_to_PerlIO(pTHX_ char *mode, ...)
{
char *mode; /* "w", "r", etc */
const char *layers = ":APR"; /* the layer name */
PerlIO *f = PerlIO_allocate(aTHX);
if (!f) {
return NULL;
}
PerlIO_apply_layers(aTHX_ f, mode, layers);
if (f) {
PerlIOAPR *st = PerlIOSelf(f, PerlIOAPR);
/* fill in the st struct, as in _open() */
st->file = file;
PerlIOBase(f)->flags |= PERLIO_F_OPEN;
return f;
}
return NULL;
}
修正/新增標示為 XXX 的地方的文件。
未指定層處理錯誤的方式。例如,何時應該明確設定 $!,何時應該將錯誤處理委派給頂層。
可能提供一些關於使用 SETERRNO() 或指標的提示,說明可以在哪裡找到它們。
我想提供一些具體範例會有所幫助,讓理解 API 變得更容易。我當然同意 API 必須簡潔,但由於沒有第二份文件更像指南,因此我認為從 API 文檔開始會更容易,但在不清楚的地方提供範例,給還不是 PerlIO 專家的使用者。