目錄

名稱

perlxs - XS 語言參考手冊

描述

簡介

XS 是一種介面描述檔案格式,用於創建 Perl 與 C 代碼(或 C 函式庫)之間的擴展介面,希望與 Perl 一起使用。XS 介面與庫結合在一起,創建一個新的庫,可以動態加載或靜態鏈接到 perl 中。XS 介面描述以 XS 語言編寫,是 Perl 擴展介面的核心組件。

在編寫 XS 之前,請閱讀下面的「注意事項」部分。

XSUB 構成 XS 介面的基本單元。經由 xsubpp 編譯器編譯後,每個 XSUB 都對應一個 C 函式定義,該定義提供了 Perl 調用約定和 C 調用約定之間的粘合劑。

粘合代碼從 Perl 堆棧中提取參數,將這些 Perl 值轉換為 C 函式期望的格式,調用此 C 函式,然後將 C 函式的返回值轉回 Perl。這裡的返回值可以是傳統的 C 返回值,也可以是任何可作為輸出參數的 C 函式參數。這些返回值可以通過將它們放在 Perl 堆棧上或修改從 Perl 那邊提供的參數來傳回 Perl。

上述是一個較簡化的觀點。由於 Perl 允許比 C 更靈活的調用約定,因此實際上 XSUB 可能做的更多,例如檢查輸入參數的有效性,如果從 C 函式返回的返回值表示失敗,則拋出異常(或返回 undef/空列表),根據參數的數字和類型調用不同的 C 函式,提供面向對象的接口等等。

當然,人們可以直接用C語言編寫這樣的粘合代碼。然而,這將是一個繁瑣的任務,特別是如果需要為多個C函數編寫粘合代碼,和/或者對Perl堆棧紀律和其他類似的奧秘不夠熟悉時。XS在這里派上用場:不是手寫這些粘合C代碼,而是可以寫一個更簡潔的簡短描述,說明應該由粘合完成的工作,然後讓XS編譯器xsubpp處理剩下的事情。

XS語言允許描述C例程的使用方式與相應的Perl例程的使用方式之間的映射。它還允許創建直接翻譯為C代碼且與預先存在的C函數無關的Perl例程。在C接口與Perl接口重合的情況下,XSUB聲明幾乎與C函數聲明(以K&R風格)相同。在這種情況下,還有另一個名為h2xs的工具,它能夠將整個C頭文件轉換為相應的XS文件,該文件將為頭文件中描述的函數/宏提供粘合。

XS編譯器被稱為xsubpp。該編譯器創建了讓XSUB操作Perl值所需的結構,並創建了讓Perl調用XSUB所需的粘合。該編譯器使用typemaps來確定如何將C函數參數和輸出值映射到Perl值,以及反向。默認的typemap(隨Perl一起提供)處理許多常見的C類型。可能還需要一個補充typemap來處理鏈接的庫的任何特殊結構和類型。有關typemap的更多信息,請參見perlxstypemap。

XS格式的文件以C語言部分開始,直到第一個MODULE =指令。其他XS指令和XSUB定義可以跟隨此行。文件中這部分所使用的“語言”通常被稱為XS語言。xsubpp識別並跳過C和XS語言部分的POD(參見perlpod),這使得XS文件可以包含嵌入式文檔。

請參見perlxstut,了解整個擴展創建過程的教程。

注意:對於某些擴展,Dave Beazley的SWIG系統可能提供了一個更為方便的機制來創建擴展的粘合代碼。有關更多信息,請參見http://www.swig.org/。

對於對C庫以及其他機器碼庫的簡單綁定,請考慮使用CPAN模塊,如FFI::Platypus或FFI::Raw,通過更簡單的libffi接口而不是XS。

On The Road

以下範例將專注於創建 Perl 與 ONC+ RPC 綁定庫函數之間的介面。rpcb_gettime() 函數用於展示 XS 語言的許多特性。此函數有兩個參數;第一個是輸入參數,第二個是輸出參數。該函數還返回一個狀態值。

bool_t rpcb_gettime(const char *host, time_t *timep);

從 C 語言調用此函數將使用以下語句。

#include <rpc/rpc.h>
bool_t status;
time_t timep;
status = rpcb_gettime( "localhost", &timep );

如果創建了一個 XSUB 以提供此函數與 Perl 之間的直接轉換,那麼這個 XSUB 將從 Perl 中使用以下代碼。$status 和 $timep 變量將包含函數的輸出。

use RPC;
$status = rpcb_gettime( "localhost", $timep );

以下 XS 檔案顯示了一個 XS 子例程或 XSUB,它演示了對 rpcb_gettime() 函數的一種可能的接口。這個 XSUB 表示了 C 與 Perl 之間的直接轉換,因此甚至從 Perl 保留了介面。這個 XSUB 將從 Perl 中使用上面顯示的用法調用。請注意,第一個三個 #include 語句,對於 EXTERN.hperl.hXSUB.h,將始終存在於 XS 檔案的開頭。此方法和其他方法將在本文中稍後擴展。應該存在一個用於更有效地獲取解釋器上下文的 PERL_NO_GET_CONTEXT 的 #define,詳細信息請參見 perlguts

#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <rpc/rpc.h>

MODULE = RPC  PACKAGE = RPC

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep
   OUTPUT:
     timep

包括包含 XSUBs 的 Perl 擴展在內的任何 Perl 擴展都應該有一個 Perl 模塊來作為引導程序,將擴展引入 Perl。此模塊將將擴展的函數和變量導出到 Perl 程序,並將擴展的 XSUBs 鏈接到 Perl 中。本文中的大多數範例將使用以下模塊,並應該如前所示使用 Perl 的 use 命令使用。Perl 模塊將在本文後面更詳細地解釋。

package RPC;

require Exporter;
require DynaLoader;
@ISA = qw(Exporter DynaLoader);
@EXPORT = qw( rpcb_gettime );

bootstrap RPC;
1;

在本文中,將探討對 rpcb_gettime() XSUB 的各種接口。這些 XSUBs 將以不同的順序接收其參數,或者將接收不同數量的參數。在每種情況下,XSUB 是 Perl 和真實 C rpcb_gettime() 函數之間的抽象,XSUB 必須始終確保使用正確的參數調用真實的 rpcb_gettime() 函數。這種抽象將允許程序員創建一個更類似 Perl 的界面以使用 C 函數。

XSUB 的結構

最簡單的 XSUB 包括 3 部分:返回值的描述、XSUB 例程的名稱及其參數的名稱,以及參數的類型或格式的描述。

以下 XSUB 允許 Perl 程序訪問名為 sin() 的 C 库函數。XSUB 將模仿接受單個參數並返回單個值的 C 函數。

double
sin(x)
  double x

可選地,可以將類型描述和參數名稱列表合併,將其重寫為

double
sin(double x)

這使得這個 XSUB 看起來類似於 ANSI C 的聲明。在參數列表後面允許使用可選的分號,如

double
sin(double x);

具有 C 指針類型的參數可能具有不同的語義:具有相似聲明的 C 函數

bool string_looks_as_a_number(char *s);
bool make_char_uppercase(char *c);

在完全不兼容的方式中使用。這些函數的參數可以被描述為像這樣的 xsubpp

char *  s
char    &c

這兩個 XS 聲明都對應於 C 類型的 char*,但它們具有不同的語義,請參閱 "The & Unary Operator"

方便起見,可以將間接運算符 * 視為類型的一部分,將地址運算符 & 視為變量的一部分。有關在 C 類型中處理限定詞和一元運算符的更多信息,請參閱 perlxstypemap

函數名稱和返回類型必須放在單獨的行上並且應該是左對齊的。

  INCORRECT                        CORRECT

  double sin(x)                    double
    double x                       sin(x)
				     double x

函數描述的其餘部分可以縮進或左對齊。下面的示例顯示了一個具有其主體左對齊的函數。本文檔中的大多數示例將縮進主體以提高可讀性。

CORRECT

double
sin(x)
double x

更複雜的 XSUB 可能包含許多其他部分。 XSUB 的每個部分都以相應的關鍵字開始,例如 INIT: 或 CLEANUP:。但是,XSUB 的前兩行始終包含相同的數據:返回類型的描述以及函數及其參數的名稱。這些之後立即跟隨的被視為 INPUT: 部分,除非顯式標記有另一個關鍵字。 (參見 "The INPUT: Keyword"。)

XSUB 部分會持續到找到另一個部分開始的關鍵字為止。

參數堆疊

Perl 參數堆疊用於存儲作為參數傳遞給 XSUB 的值以及存儲 XSUB 的返回值。實際上,所有 Perl 函數(包括非 XSUB 的函數)都將它們的值存儲在此堆疊上,每個函數限於其自己的範圍內的堆疊位置。在本文檔中,屬於活動函數的該堆疊上的第一個位置將被稱為該函數的位置 0。

XSUB 使用宏 ST(x) 引用它們的堆疊參數,其中 x 是堆疊中此 XSUB 部分的位置。該函數的位置 0 將被 XSUB 稱為 ST(0)。 XSUB 的傳入參數和傳出的返回值始終從 ST(0) 開始。對於許多簡單情況,xsubpp 編譯器將生成處理參數堆疊所需的代碼,方法是嵌入在類型映射中找到的代碼片段。在更複雜的情況下,程序員必須提供代碼。

RETVAL 變數

RETVAL 變數是一個特殊的 C 變數,會自動為您宣告。RETVAL 的 C 類型與 C 库函數的返回類型相匹配。xsubpp 編譯器將在每個具有非 void 返回類型的 XSUB 中宣告此變數。默認情況下,生成的 C 函數將使用 RETVAL 來保存調用的 C 库函數的返回值。在簡單情況下,RETVAL 的值將放置在參數堆疊的 ST(0) 中,Perl 可以將其作為 XSUB 的返回值接收。

如果 XSUB 具有 void 返回類型,則編譯器將不為該函數宣告 RETVAL 變數。在使用 PPCODE: 部分時,不需要對 RETVAL 變數進行任何操作,該部分可以使用直接的堆疊操作將輸出值放置在堆疊上。

如果未使用 PPCODE: 指示,則只應對不返回值的子例程使用 void 返回值,即使使用了明確設置 ST(0) 的 CODE: 指示。

本文檔的較舊版本建議在此類情況下使用 void 返回值。後來發現,在 XSUB 真正是 void 時,這可能導致段錯誤。這種做法現已廢棄,並且在未來的某個版本中可能不再支持。在這些情況下使用返回值 SV *。(目前 xsubpp 包含一些啟發式代碼,試圖區分“真正的 void”和“將作為舊實踐聲明為 void”的函數。因此,除非使用 SV * 作為返回值,否則您的代碼將受到此啟發式的影響。)

透過 RETVAL 返回 SVs、AVs 和 HVs

當您使用 RETVAL 返回 SV * 時,幕後會有一些魔法需要提及。例如,當您使用 ST(x) 宏來操作參數堆疊時,通常需要特別注意引用計數。(有關引用計數的更多信息,請參見 perlguts。)為了使您的生活更輕鬆,typemap 文件在您返回 SV * 時會自動使 RETVAL 成為致命的。因此,以下兩個 XSUB 大致相等

void
alpha()
    PPCODE:
        ST(0) = newSVpv("Hello World",0);
        sv_2mortal(ST(0));
        XSRETURN(1);

SV *
beta()
    CODE:
        RETVAL = newSVpv("Hello World",0);
    OUTPUT:
        RETVAL

這非常有用,因為它通常可以提高可讀性。雖然對於 SV *,這樣可以正常工作,但是將 AV * 或 HV * 作為返回值並不容易。您應該能夠編寫

AV *
array()
    CODE:
        RETVAL = newAV();
        /* do something with RETVAL */
    OUTPUT:
        RETVAL

但是,由於 typemap 文件中存在一個無法修復的錯誤(修復它會破壞大量現有的 CPAN 模塊),AV * 的引用計數未正確遞減。因此,上面的 XSUB 在被調用時將會洩漏內存。對於 HV *、CV * 和 SVREF(表示標量引用,而不是一般的 SV *),也存在同樣的問題。在以 perl 5.16 開始的 perl 上的 XS 代碼中,您可以使用具有正確處理引用計數的版本來覆蓋這些類型的 typemap。在您的 TYPEMAP 部分中,執行

AV*	T_AVREF_REFCOUNT_FIXED

要獲得修復後的變體。為了與舊版本的perl向後兼容,您可以在返回上述類型之一時,手動減少引用計數,使用sv_2mortal

AV *
array()
    CODE:
        RETVAL = newAV();
        sv_2mortal((SV*)RETVAL);
        /* do something with RETVAL */
    OUTPUT:
        RETVAL

請記住,對於SV *,您不必這樣做。所有核心類型映射的參考文檔都可以在perlxstypemap中找到。

MODULE 關鍵字

MODULE 關鍵字用於開始 XS 代碼並指定正在定義的函數所屬的包。第一個 MODULE 關鍵字之前的所有文本都被視為 C 代碼,並通過 POD 剝離後傳遞到輸出,但其餘不受影響。每個 XS 模塊都將有一個用於將 XSUBs 鉤接到 Perl 中的啟動函數。此啟動函數的包名將與 XS 源文件中最後一個 MODULE 聲明的值匹配。MODULE 的值應在同一個 XS 文件中始終保持不變,儘管這不是必需的。

以下示例將開始 XS 代碼並將所有函數放置在名為 RPC 的包中。

MODULE = RPC

PACKAGE 關鍵字

當 XS 源文件中的函數必須分離為不同的包時,應使用 PACKAGE 關鍵字。此關鍵字與 MODULE 關鍵字一起使用,並且在使用時必須緊接其後。

MODULE = RPC  PACKAGE = RPC

[ XS code in package RPC ]

MODULE = RPC  PACKAGE = RPCB

[ XS code in package RPCB ]

MODULE = RPC  PACKAGE = RPC

[ XS code in package RPC ]

同一個包名可以多次使用,允許不連續的代碼。如果您具有比包名更強的排序原則,這將很有用。

雖然此關鍵字是可選的,在某些情況下提供了冗余信息,但應始終使用。此關鍵字將確保 XSUBs 出現在所需的包中。

PREFIX 關鍵字

PREFIX 關鍵字指定應從 Perl 函數名中刪除的前綴。如果 C 函數是 rpcb_gettime(),並且 PREFIX 值為 rpcb_,則 Perl 將此函數視為 gettime()

當使用時,此關鍵字應跟在 PACKAGE 關鍵字之後。如果未使用 PACKAGE,則 PREFIX 應跟在 MODULE 關鍵字之後。

MODULE = RPC  PREFIX = rpc_

MODULE = RPC  PACKAGE = RPCB  PREFIX = rpcb_

OUTPUT: 關鍵字

輸出:關鍵字表示在 XSUB 終止時應更新某些函數參數(使新值對 Perl 可見),或表示應將某些值返回給呼叫的 Perl 函數。對於沒有 CODE: 或 PPCODE: 部分的簡單函數(例如上面的 sin() 函數),RETVAL 變數會自動指定為輸出值。對於更複雜的函數,xsubpp 編譯器將需要幫助來確定哪些變數是輸出變數。

此關鍵字通常會用於補充 CODE: 關鍵字。當存在 CODE: 關鍵字時,RETVAL 變數不會被識別為輸出變數。在這種情況下,使用 OUTPUT: 關鍵字告訴編譯器 RETVAL 真的是一個輸出變數。

OUTPUT: 關鍵字還可以用於指示函數參數為輸出變數。當函數內部修改了參數並且程序員希望 Perl 看到更新時,可能需要這樣做。

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep
   OUTPUT:
     timep

OUTPUT: 關鍵字還允許將輸出參數映射到匹配的程式碼片段,而不是類型映射。

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep
   OUTPUT:
     timep sv_setnv(ST(1), (double)timep);

xsubpp 對 XSUB 的 OUTPUT 部分中的所有參數(除 RETVAL 外)發出自動的 SvSETMAGIC()。這通常是期望的行為,因為它負責在輸出參數上適當地調用 'set' magic(對於必須在不存在時創建的哈希或數組元素參數非常重要)。如果出於某種原因不希望此行為,OUTPUT 部分可以包含一行 SETMAGIC: DISABLE 來禁用它,以禁用 OUTPUT 部分的其餘參數。同樣,SETMAGIC: ENABLE 可用於重新啟用其餘 OUTPUT 部分的該行為。有關 'set' magic 的更多細節,請參見 perlguts。

NO_OUTPUT 關鍵字

NO_OUTPUT 可以放在 XSUB 的第一個標記位置。此關鍵字表示,雖然我們提供一個介面給的 C 子程式具有非 void 返回類型,但此 C 子程式的返回值不應該從生成的 Perl 子程式返回。

有了此關鍵字,會建立 "The RETVAL Variable",並且在對子程序的生成呼叫中,此變數將被分配,但是此變數的值不會在自動生成的程式碼中使用。

此關鍵字僅在使用者提供的程式碼將存取RETVAL時才有意義。特別是在 C 返回值僅為錯誤條件指示時,尤其有用,以使函數介面更符合 Perl 慣例。

  NO_OUTPUT int
  delete_file(char *name)
    POSTCALL:
      if (RETVAL != 0)
	  croak("Error %d while deleting file '%s'", RETVAL, name);

例如,在此生成的 XS 函數在成功時不返回任何內容,在錯誤時將會使用具意義的錯誤訊息執行 die()。

CODE 關鍵字

此關鍵字用於較複雜的 XSUB 中,需要對 C 函數進行特殊處理。RETVAL 變數仍然被宣告,但除非在 OUTPUT: 部分中指定,否則不會返回。

以下 XSUB 適用於需要對其參數進行特殊處理的 C 函數。首先給出 Perl 用法。

$status = rpcb_gettime( "localhost", $timep );

接著是 XSUB。

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t timep
   CODE:
          RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

INIT 關鍵字

INIT 關鍵字允許在編譯器生成對 C 函數的呼叫之前將初始化插入到 XSUB 中。與上述 CODE 關鍵字不同,此關鍵字不會影響編譯器處理 RETVAL 的方式。

    bool_t
    rpcb_gettime(host,timep)
          char *host
          time_t &timep
	INIT:
	  printf("# Host is %s\n", host );
        OUTPUT:
          timep

INIT 部分的另一種用法是在調用 C 函數之前檢查前提條件。

    long long
    lldiv(a,b)
	long long a
	long long b
      INIT:
	if (a == 0 && b == 0)
	    XSRETURN_UNDEF;
	if (b == 0)
	    croak("lldiv: cannot divide by 0");

NO_INIT 關鍵字

NO_INIT 關鍵字用於指示函數參數僅用作輸出值。xsubpp 編譯器通常會生成代碼,從參數堆疊中讀取所有函數參數的值,並在進入函數時將它們分配給 C 變量。NO_INIT 會告訴編譯器某些參數將用於輸出而不是輸入,並且它們將在函數終止之前處理。

以下示例展示了 rpcb_gettime() 函數的變化。此函數僅將 timep 變量用作輸出變量,並且不關心其初始內容。

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep = NO_INIT
   OUTPUT:
     timep

TYPEMAP 關鍵字

從 Perl 5.16 開始,您可以將 typemaps 嵌入到您的 XS 代碼中,而不是或除了在單獨文件中的 typemaps 之外。多個此類嵌入式 typemaps 將按照在 XS 代碼中出現的順序進行處理,就像本地 typemap 文件一樣,它們比默認 typemap 優先,嵌入式 typemaps 可以覆蓋 TYPEMAP、INPUT 和 OUTPUT 段的先前定義。嵌入式 typemaps 的語法是

TYPEMAP: <<HERE
... your typemap code here ...
HERE

其中 TYPEMAP 關鍵字必須出現在新行的第一列。

有關編寫 typemaps 的詳細資訊,請參閱 perlxstypemap

初始化函數參數

C 函數參數通常使用它們從參數堆疊中的值初始化(參數堆疊又包含了從 Perl 傳遞給 XSUB 的參數)。typemaps 包含用於將 Perl 值轉換為 C 參數的程式碼片段。然而,程式設計師可以覆蓋 typemaps 並提供替代(或附加)初始化程式碼。初始化程式碼從 INPUT: 部分的第一個 =;+ 開始的行開始。唯一的例外是,如果此 ; 終止該行,則此 ; 將被靜默忽略。

以下代碼示範了如何為函數參數提供初始化程式碼。初始化程式碼由編譯器在加入輸出之前在雙引號內進行評估,因此任何應該被直接解釋的內容 [主要是 $@\\] 必須使用反斜線進行保護。變數 $var$arg 和 <

bool_t
rpcb_gettime(host,timep)
     char *host = (char *)SvPVbyte_nolen($arg);
     time_t &timep = 0;
   OUTPUT:
     timep

這不應該用於提供參數的默認值。通常情況下,當函數參數必須在使用之前由另一個庫函數處理時,才會使用此功能。默認參數將在下一節中介紹。

如果初始化以 = 開始,則它將在輸入變量的聲明中輸出,取代類型映射提供的初始化。如果初始化以 ;+ 開始,則它將在聲明所有輸入變量後執行。在 ; 情況下,通常不執行類型映射提供的初始化。對於 + 情況,變量的聲明將包括來自類型映射的初始化。全局變量 %v 可用於真正罕見的情況,其中一個初始化的信息需要在另一個初始化中使用。

以下是一個真正模糊的示例

bool_t
rpcb_gettime(host,timep)
     time_t &timep; /* \$v{timep}=@{[$v{timep}=$arg]} */
     char *host + SvOK($v{timep}) ? SvPVbyte_nolen($arg) : NULL;
   OUTPUT:
     timep

上面示例中使用的構造 \$v{timep}=@{[$v{timep}=$arg]} 具有兩個目的:首先,當此行被 xsubpp 處理時,Perl 片段 $v{timep}=$arg 被評估。其次,評估片段的文本被輸出到生成的 C 文件中(在 C 註釋內)!在處理 char *host 行時,$arg 將評估為 ST(0),並且 $v{timep} 將評估為 ST(1)

默認參數值

可以通過在參數列表中放置賦值語句來指定 XSUB 參數的默認值。默認值可以是數字、字符串或特殊字符串 NO_INIT。默認值應該始終僅用於最右邊的參數。

為了允許 XSUB 對 rpcb_gettime() 有一個默認的主機值,可以重新排列 XSUB 的參數。然後,XSUB 將使用正確順序的參數調用真正的 rpcb_gettime() 函數。可以使用以下語句之一從 Perl 中調用此 XSUB

$status = rpcb_gettime( $timep, $host );

$status = rpcb_gettime( $timep );

XSUB 將看起來像以下代碼。CODE: 块用於使用正確順序的參數調用真正的 rpcb_gettime() 函數。

bool_t
rpcb_gettime(timep,host="localhost")
     char *host
     time_t timep = NO_INIT
   CODE:
          RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

PREINIT: 關鍵字

PREINIT: 關鍵字允許在輸入:部分的參數聲明之前或之後立即聲明額外的變量。

如果變量在 CODE: 部分中聲明,則它將跟隨任何為輸入參數發出的類型映射代碼。這可能導致聲明最終在 C 代碼之後,這是 C 語法錯誤。在使用顯式的 ;-type 或 +-type 參數初始化時,可能會出現類似的錯誤(請參見"初始化函數參數")。在 INIT: 部分中聲明這些變量將無濟於事。

在這種情況下,為了強制一個附加變量與其他變量的聲明一起被聲明,將聲明放入 PREINIT: 部分中。PREINIT: 關鍵字可以在 XSUB 中使用一次或多次。

以下的範例是等價的,但如果程式碼使用了複雜的型態映射,那麼第一個範例會更安全。

     bool_t
     rpcb_gettime(timep)
          time_t timep = NO_INIT
	PREINIT:
          char *host = "localhost";
        CODE:
	  RETVAL = rpcb_gettime( host, &timep );
        OUTPUT:
          timep
          RETVAL

對於這種特殊情況,INIT: 關鍵字會生成與 PREINIT: 關鍵字相同的 C 程式碼。另一個正確但容易出錯的例子

     bool_t
     rpcb_gettime(timep)
          time_t timep = NO_INIT
	CODE:
          char *host = "localhost";
	  RETVAL = rpcb_gettime( host, &timep );
        OUTPUT:
          timep
          RETVAL

宣告 host 的另一種方式是在 CODE: 區塊中使用 C 塊

     bool_t
     rpcb_gettime(timep)
          time_t timep = NO_INIT
	CODE:
	  {
            char *host = "localhost";
	    RETVAL = rpcb_gettime( host, &timep );
	  }
        OUTPUT:
          timep
          RETVAL

在處理型態映射轉換操作一些全局狀態時,能夠在型態映射條目被處理之前放置額外的宣告非常方便。

    MyObject
    mutate(o)
	PREINIT:
	    MyState st = global_state;
	INPUT:
	    MyObject o;
	CLEANUP:
	    reset_to(global_state, st);

在這裡,我們假設在 INPUT: 區段進行到 MyObject 的轉換,並在處理 RETVAL 時從 MyObject 轉換時,會修改一個全局變數 global_state。在進行這些轉換之後,我們還原 global_state 的舊值(例如,為了避免記憶體洩漏)。

還有一種方法可以將清晰度換成緊湊性:INPUT 區段允許宣告 C 變數,這些變數不會出現在子程序的參數列表中。因此,mutate() 的上述程式碼可以重寫為

    MyObject
    mutate(o)
	  MyState st = global_state;
	  MyObject o;
	CLEANUP:
	  reset_to(global_state, st);

而 rpcb_gettime() 的程式碼可以重寫為

     bool_t
     rpcb_gettime(timep)
	  time_t timep = NO_INIT
	  char *host = "localhost";
	C_ARGS:
	  host, &timep
	OUTPUT:
          timep
          RETVAL

SCOPE: 關鍵字

SCOPE: 關鍵字允許為特定的 XSUB 啟用作用域。如果啟用,XSUB 將自動調用 ENTER 和 LEAVE。

為了支援潛在的複雜型態映射,如果一個 XSUB 使用的型態映射條目包含像 /*scope*/ 的註釋,則該 XSUB 將自動啟用作用域。

啟用作用域

SCOPE: ENABLE

禁用作用域

SCOPE: DISABLE

INPUT: 關鍵字

XSUB 的參數通常在進入 XSUB 後立即評估。INPUT: 關鍵字可用於稍後強制評估這些參數。INPUT: 關鍵字可以在 XSUB 內使用多次,並且可用於列出一個或多個輸入變數。此關鍵字與 PREINIT: 關鍵字一起使用。

以下範例顯示如何延遲評估輸入參數 timep,在 PREINIT 之後。

    bool_t
    rpcb_gettime(host,timep)
          char *host
	PREINIT:
	  time_t tt;
	INPUT:
          time_t timep
        CODE:
               RETVAL = rpcb_gettime( host, &tt );
	       timep = tt;
        OUTPUT:
          timep
          RETVAL

下一個範例顯示每個輸入參數都是延遲評估的。

    bool_t
    rpcb_gettime(host,timep)
	PREINIT:
	  time_t tt;
	INPUT:
          char *host
	PREINIT:
	  char *h;
	INPUT:
          time_t timep
        CODE:
	       h = host;
	       RETVAL = rpcb_gettime( h, &tt );
	       timep = tt;
        OUTPUT:
          timep
          RETVAL

由於 INPUT 區段允許宣告不出現在子程序參數列表中的 C 變數,因此這可以縮短為

    bool_t
    rpcb_gettime(host,timep)
	  time_t tt;
          char *host;
	  char *h = host;
          time_t timep;
        CODE:
	  RETVAL = rpcb_gettime( h, &tt );
	  timep = tt;
        OUTPUT:
          timep
          RETVAL

(我們利用我們對於char *輸入轉換是一個“簡單”的知識,因此host在宣告行上初始化,而我們的分配h = host不會太早執行。否則,需要在CODE:或INIT:部分中進行分配h = host。)

IN/OUTLIST/IN_OUTLIST/OUT/IN_OUT 關鍵字

在 XSUB 的參數列表中,可以在參數名稱前加上IN/OUTLIST/IN_OUTLIST/OUT/IN_OUT關鍵字。IN 關鍵字是默認值,其他關鍵字表示 Perl 介面應該如何與 C 介面有所不同。

OUTLIST/IN_OUTLIST/OUT/IN_OUT關鍵字開頭的參數被認為是由 C 子例程通過指針使用的。OUTLIST/OUT關鍵字表示 C 子例程不檢查此參數指向的記憶體,但會通過此指針進行寫入以提供額外的返回值。

OUTLIST關鍵字開頭的參數不會出現在生成的 Perl 函數的使用簽名中。

IN_OUTLIST/IN_OUT/OUT開頭的參數會作為 Perl 函數的參數出現。除了OUT參數之外,這些參數會被轉換為相應的 C 類型,然後將這些數據的指針作為引數傳遞給 C 函數。預期 C 函數將通過這些指針進行寫入。

生成的 Perl 函數的返回列表由函數的 C 返回值組成(除非 XSUB 的返回類型為void或使用了The NO_OUTPUT Keyword),後跟所有OUTLISTIN_OUTLIST參數(按出現順序)。從 XSUB 返回時,IN_OUT/OUT Perl 參數將被修改為由 C 函數寫入的值。

例如,一個 XSUB

void
day_month(OUTLIST day, IN unix_time, OUTLIST month)
  int day
  int unix_time
  int month

應該在 Perl 中使用如下

my ($day, $month) = day_month(time);

對應函數的 C 簽名應該是

void day_month(int *day, int unix_time, int *month);

IN/OUTLIST/IN_OUTLIST/IN_OUT/OUT 關鍵字可以與 ANSI 樣式聲明混合使用,如

void
day_month(OUTLIST int day, int unix_time, OUTLIST int month)

(這裡省略了可選的IN關鍵字)。

IN_OUT參數與使用“The & Unary Operator”介紹的參數相同,並放在OUTPUT:部分中(參見“The OUTPUT: Keyword”)。IN_OUTLIST參數非常類似,唯一的區別是 C 函數通過指針寫入的值不會修改 Perl 參數,但會放在輸出列表中。

OUTLIST/OUT參數與IN_OUTLIST/IN_OUT參數的區別僅在於 Perl 參數的初始值未被讀取(也未被提供給 C 函數 - 該函數會得到一些垃圾)。例如,上述相同的 C 函數可以這樣進行接口化

void day_month(OUT int day, int unix_time, OUT int month);

void
day_month(day, unix_time, month)
    int &day = NO_INIT
    int  unix_time
    int &month = NO_INIT
  OUTPUT:
    day
    month

然而,生成的 Perl 函数以非常 C 式的方式调用

my ($day, $month);
day_month($day, time, $month);

length(NAME) 關鍵字

如果 C 函数的其中一個輸入參數是字符串參數 NAME 的長度,則可以在 XSUB 声明中使用 length(NAME) 來替換長度參數的名稱。在調用生成的 Perl 函数時,必須省略此參數。例如,

void
dump_chars(char *s, short l)
{
  short n = 0;
  while (n < l) {
      printf("s[%d] = \"\\%#03o\"\n", n, (int)s[n]);
      n++;
  }
}

MODULE = x		PACKAGE = x

void dump_chars(char *s, short length(s))

應該呼叫為 dump_chars($string)

此指令僅支援 ANSI 型態的函數聲明。

變長參數列表

通過在參數列表中指定省略號 (...),XSUB 可以具有可變長度的參數列表。這種省略號的使用類似於 ANSI C 中找到的用法。程序員可以通過檢查 items 變量來確定傳遞給 XSUB 的參數數量,該變量由 xsubpp 編譯器為所有 XSUB 提供。通過使用此機制,可以創建一個接受未知長度參數列表的 XSUB。

rpcb_gettime() XSUB 的 host 參數可以是可選的,因此省略號可以用來指示 XSUB 將接受可變數量的參數。Perl 應該能夠使用以下任何一種語句來調用此 XSUB。

$status = rpcb_gettime( $timep, $host );

$status = rpcb_gettime( $timep );

帶有省略號的 XS 代碼如下。

     bool_t
     rpcb_gettime(timep, ...)
          time_t timep = NO_INIT
	PREINIT:
          char *host = "localhost";
        CODE:
	  if( items > 1 )
	       host = (char *)SvPVbyte_nolen(ST(1));
	  RETVAL = rpcb_gettime( host, &timep );
        OUTPUT:
          timep
          RETVAL

The C_ARGS: 關鍵字

C_ARGS: 關鍵字允許創建與 Perl 和 C 的調用序列不同的 XSUBS,而無需編寫 CODE: 或 PPCODE: 部分。 C_ARGS: 段落的內容作為調用的 C 函数的參數,而不作任何更改。

例如,假設一個 C 函数被聲明為

symbolic nth_derivative(int n, symbolic function, int flags);

並且默認標誌存儲在全局 C 變量 default_flags 中。假設你想創建一個這樣的接口

$second_deriv = $function->nth_derivative(2);

為此,請將 XSUB 声明為

    symbolic
    nth_derivative(function, n)
	symbolic	function
	int		n
      C_ARGS:
	n, function, default_flags

The PPCODE: 關鍵字

PPCODE: 關鍵字是 CODE: 關鍵字的另一種形式,用於告訴 xsubpp 編譯器程序員正在提供代碼來控制 XSUB 的返回值的參數堆棧。有時,人們希望 XSUB 返回一系列值而不是單個值。在這些情況下,必須使用 PPCODE:,然後明確地將值列表壓入堆棧。PPCODE: 和 CODE: 關鍵字不應在同一個 XSUB 中一起使用。

PPCODE: 和 CODE: 區段之間的實際差異在於 SP 宏的初始化(代表 目前的 Perl 堆疊指標),以及在從 XSUB 返回時處理堆疊上的資料。在 CODE: 區段中,SP 保留了進入 XSUB 時的值:SP 在函數指標上(即最後一個參數之後)。在 PPCODE: 區段中,SP 往回移動到參數列表的開頭,這使得 PUSH*() 宏能夠將輸出值放置在 Perl 預期的位置,當 XSUB 返回 Perl 時。

CODE: 區段的生成尾部確保 Perl 將看到的返回值數量為 0 或 1(取決於 C 函數返回值的 void 性質,以及在 "The RETVAL Variable" 中提到的啟發式方法)。對於 PPCODE: 區段生成的尾部則基於返回值的數量和 [X]PUSH*() 宏更新 SP 的次數。

請注意,宏 ST(i)XST_m*()XSRETURN*() 在 CODE: 區段和 PPCODE: 區段中同樣適用。

以下的 XSUB 將調用 C rpcb_gettime() 函數並將其兩個輸出值,timep 和 status,作為單個列表返回給 Perl。

     void
     rpcb_gettime(host)
          char *host
	PREINIT:
          time_t  timep;
          bool_t  status;
        PPCODE:
          status = rpcb_gettime( host, &timep );
          EXTEND(SP, 2);
          PUSHs(sv_2mortal(newSViv(status)));
          PUSHs(sv_2mortal(newSViv(timep)));

請注意,程式設計師必須提供必要的 C 代碼,以便調用真正的 rpcb_gettime() 函數並將返回值正確放置在參數堆疊上。

此函數的 void 返回類型告訴 xsubpp 編譯器不需要或不使用 RETVAL 變數,並且不應該創建它。在大多數情況下,應該使用 void 返回類型與 PPCODE: 指令。

EXTEND() 宏用於在參數堆疊上為 2 個返回值預留空間。PPCODE: 指令使 xsubpp 編譯器創建一個名為 SP 的堆疊指標,而正是這個指標被 EXTEND() 宏使用。然後使用 PUSHs() 宏將值推送到堆疊上。

現在可以使用以下語句從 Perl 使用 rpcb_gettime() 函數。

($status, $timep) = rpcb_gettime("localhost");

在處理 PPCODE 區段的輸出參數時,請確保正確處理 'set' magic。有關 'set' magic 的詳細信息,請參見 perlguts

回傳 Undef 和空列表

有時程式設計師會希望如果函數失敗,而不是返回單獨的狀態值,而僅僅返回 undef 或空列表。rpcb_gettime() 函數正好提供了這種情況。如果函數成功,我們希望它返回時間,如果失敗,我們希望返回 undef。在以下 Perl 代碼中,$timep 的值將是 undef 或有效的時間。

$timep = rpcb_gettime( "localhost" );

以下的 XSUB 只是使用 SV * 返回類型作為記憶提示,並使用 CODE: 块來告知編譯器程式設計師已提供了所有必要的代碼。sv_newmortal() 調用將返回值初始化為 undef,使其成為默認返回值。

     SV *
     rpcb_gettime(host)
          char *  host
	PREINIT:
          time_t  timep;
          bool_t x;
        CODE:
          ST(0) = sv_newmortal();
          if( rpcb_gettime( host, &timep ) )
               sv_setnv( ST(0), (double)timep);

下一個例子演示了如何在需要時將顯式 undef 放入返回值中。

     SV *
     rpcb_gettime(host)
          char *  host
	PREINIT:
          time_t  timep;
          bool_t x;
        CODE:
          if( rpcb_gettime( host, &timep ) ){
               ST(0) = sv_newmortal();
               sv_setnv( ST(0), (double)timep);
          }
          else{
               ST(0) = &PL_sv_undef;
          }

要返回一個空列表,必須使用 PPCODE: 块,然後不要將返回值推送到堆棧上。

     void
     rpcb_gettime(host)
          char *host
	PREINIT:
          time_t  timep;
        PPCODE:
          if( rpcb_gettime( host, &timep ) )
               PUSHs(sv_2mortal(newSViv(timep)));
          else{
	      /* Nothing pushed on stack, so an empty
	       * list is implicitly returned. */
          }

有些人可能傾向於在上述 XSUB 中包含一個顯式的 return,而不是讓控制落入結尾。在這些情況下,應該使用 XSRETURN_EMPTY,這將確保 XSUB 堆棧被正確調整。請參考 perlapi 了解其他 XSRETURN 宏。

由於 XSRETURN_* 宏也可以與 CODE 块一起使用,因此可以將此示例重寫為

     int
     rpcb_gettime(host)
          char *host
	PREINIT:
          time_t  timep;
        CODE:
          RETVAL = rpcb_gettime( host, &timep );
	  if (RETVAL == 0)
		XSRETURN_UNDEF;
	OUTPUT:
	  RETVAL

事實上,這個檢查也可以放在 POSTCALL: 部分中。結合 PREINIT: 簡化,這導致

     int
     rpcb_gettime(host)
          char *host
          time_t  timep;
	POSTCALL:
	  if (RETVAL == 0)
		XSRETURN_UNDEF;

REQUIRE: 關鍵字

REQUIRE: 關鍵字用於指示編譯 XS 模組所需的最低版本的 xsubpp 編譯器。包含以下語句的 XS 模組將僅編譯與 xsubpp 版本 1.922 或更高版本。

REQUIRE: 1.922

CLEANUP: 關鍵字

當 XSUB 需要在終止之前進行特殊清理程序時,可以使用此關鍵字。使用 CLEANUP: 關鍵字時,必須跟在 XSUB 中出現的任何 CODE: 或 OUTPUT: 块之後。指定的清理塊代碼將添加為 XSUB 中的最後語句。

POSTCALL: 關鍵字

當 XSUB 需要在執行 C 子程序呼叫後執行特殊程序時,可以使用此關鍵字。當使用 POSTCALL: 關鍵字時,它必須位於 XSUB 中存在的 OUTPUT: 和 CLEANUP: 區塊之前。

請參閱"NO_OUTPUT 關鍵字""返回未定義和空列表"中的示例。

當 C 子程序呼叫是由使用者提供 CODE: 或 PPCODE: 區段時,POSTCALL: 區塊並不太有意義。

BOOT: 關鍵字

BOOT: 關鍵字用於將代碼添加到擴展的啟動函數中。啟動函數由 xsubpp 編譯器生成,通常包含註冊任何 XSUBs 到 Perl 所需的語句。使用 BOOT: 關鍵字,程序員可以告訴編譯器添加額外的語句到啟動函數中。

此關鍵字可以在第一個 MODULE 關鍵字之後的任何時間使用,並應該獨立出現在一行上。關鍵字後的第一個空行將終止代碼塊。

BOOT:
# The following message will be printed when the
# bootstrap function executes.
printf("Hello from the bootstrap!\n");

VERSIONCHECK: 關鍵字

VERSIONCHECK: 關鍵字對應於 xsubpp 的 -versioncheck 和 -noversioncheck 選項。此關鍵字覆蓋命令行選項。默認情況下啟用版本檢查。當啟用版本檢查時,XS 模塊將嘗試驗證其版本是否與 PM 模塊的版本匹配。

啟用版本檢查

VERSIONCHECK: ENABLE

禁用版本檢查

VERSIONCHECK: DISABLE

請注意,如果 PM 模塊的版本是 NV(浮點數),它將被字符串化,可能會丟失精度(目前截斷到九位小數),因此它可能不再與 XS 模塊的版本匹配。建議對 $VERSION 声明加引號以使其成為字符串,如果使用長版本號。

PROTOTYPES: 關鍵字

PROTOTYPES: 關鍵字對應於 xsubpp 的 -prototypes 和 -noprototypes 選項。此關鍵字覆蓋命令行選項。默認情況下禁用原型。啟用原型時,XSUBs 將被給予 Perl 原型。此關鍵字可以在 XS 模塊中多次使用,以為模塊的不同部分啟用和禁用原型。請注意,如果您不明確啟用或禁用原型,xsubpp 將提醒您。

Please specify prototyping behavior for Foo.xs (see perlxs manual)

啟用原型

PROTOTYPES: ENABLE

禁用原型

PROTOTYPES: DISABLE

PROTOTYPE: 關鍵字

此關鍵字與上面的 PROTOTYPES: 關鍵字類似,但可用於強制 xsubpp 使用特定原型進行 XSUB。此關鍵字覆蓋所有其他原型選項和關鍵字,但僅影響當前的 XSUB。有關 Perl 原型的信息,請參閱"perlsub 中的原型"

    bool_t
    rpcb_gettime(timep, ...)
          time_t timep = NO_INIT
	PROTOTYPE: $;$
	PREINIT:
          char *host = "localhost";
        CODE:
		  if( items > 1 )
		       host = (char *)SvPVbyte_nolen(ST(1));
		  RETVAL = rpcb_gettime( host, &timep );
        OUTPUT:
          timep
          RETVAL

如果啟用了原型,您可以像以下示例中一樣在本地禁用給定 XSUB 的原型

void
rpcb_gettime_noproto()
    PROTOTYPE: DISABLE
...

ALIAS: 關鍵字

ALIAS: 關鍵字允許 XSUB 具有兩個或更多個唯一的 Perl 名稱,並知道調用時使用了哪些名稱。Perl 名稱可以是完全限定的包名。每個別名都被賦予一個索引。編譯器將設置一個名為 ix 的變量,其中包含使用的別名的索引。當使用其聲明的名稱調用 XSUB 時,ix 將為 0。

以下示例將為此函數創建別名 FOO::gettime()BAR::getit()

    bool_t
    rpcb_gettime(host,timep)
          char *host
          time_t &timep
	ALIAS:
	    FOO::gettime = 1
	    BAR::getit = 2
	INIT:
	  printf("# ix = %d\n", ix );
        OUTPUT:
          timep

當您為相同的值創建多個別名時,將生成警告。可以通過創建解析為相同值的多個定義或使用現代版本的 ExtUtils::ParseXS 來解決這個問題,您可以使用符號別名,這些別名用 => 而不是 = 表示。例如,您可以更改上面的內容,使別名部分看起來像這樣

	ALIAS:
	    FOO::gettime = 1
	    BAR::getit = 2
            BAZ::gettime => FOO::gettime

這將產生與此相同的效果

	ALIAS:
	    FOO::gettime = 1
	    BAR::getit = 2
            BAZ::gettime = 1

但後者在構建過程中將產生警告。一種與我們工具鏈的舊版本兼容的機制是這樣做

    #define FOO_GETTIME 1
    #define BAR_GETIT 2
    #define BAZ_GETTIME 1

    bool_t
    rpcb_gettime(host,timep)
          char *host
          time_t &timep
	ALIAS:
	    FOO::gettime = FOO_GETTIME
	    BAR::getit = BAR_GETIT
            BAZ::gettime = BAZ_GETTIME
	INIT:
	  printf("# ix = %d\n", ix );
        OUTPUT:
          timep

OVERLOAD: 關鍵字

您可以使用 OVERLOAD 關鍵字來定義函數的其他 Perl 名稱(就像上面的 ALIAS: 關鍵字一樣),而不是使用純 Perl 編寫重載的接口。但是,重載的函數必須以接受 perl 的重載系統提供的參數數量的方式定義。對於大多數重載方法,它將是三個參數;對於 nomethod 函數,它將是四個參數。但是,位運算符 &|^~ 可以使用三個或五個參數調用(請參見overload)。

如果任何函數具有 OVERLOAD: 關鍵字,則 xsubpp 生成的 c 文件中將定義幾行額外的行以便與重載魔術註冊。

由於祝福對象實際上是作為 RV 存儲的,因此使用 typemap 功能來預處理參數並提取祝福 RV 內部存儲的實際 SV 是有用的。請參見下面的 T_PTROBJ_SPECIAL 的示例。

要使用 OVERLOAD: 關鍵字,請創建一個接受三個輸入參數的 XS 函數(或使用 C 風格的 '...' 定義),如下所示

SV *
cmp (lobj, robj, swap)
My_Module_obj    lobj
My_Module_obj    robj
IV               swap
OVERLOAD: cmp <=>
{ /* function defined here */}

在這種情況下,該函數將重載兩個三路比較運算符。對於所有使用非字母字符的重載操作,您必須以不帶引號的方式輸入參數,用空格分隔多個重載。請注意,""(字符串化重載)應該輸入為 \"\"(即轉義)。

如上所述,由於位運算符可能需要額外的參數,您可能想使用像 (lobj, robj, swap, ...)(帶有文字 ...)作為您的參數列表。

FALLBACK: 關鍵字

除了 OVERLOAD 關鍵字外,如果您需要控制 Perl 如何自動產生缺失的過載運算子,您可以在模組標頭區段設置 FALLBACK 關鍵字,如下所示

MODULE = RPC  PACKAGE = RPC

FALLBACK: TRUE
...

其中 FALLBACK 可以取 TRUE、FALSE 或 UNDEF 三個值中的任何一個。如果在使用 OVERLOAD 時沒有設置任何 FALLBACK 值,則默認為 UNDEF。除非已經定義了一個或多個使用 OVERLOAD 的函數,否則不會使用 FALLBACK。請參閱overload 中的 "fallback"以獲取更多詳細信息。

INTERFACE: 關鍵字

此關鍵字將當前的 XSUB 声明為給定調用簽名的保管者。如果該關鍵字後跟有一些文本,則將其視為具有該簽名的函數列表,並應將其附加到當前的 XSUB。

例如,如果您有 4 個 C 函數 multiply()、divide()、add()、subtract(),它們都具有相同的簽名

symbolic f(symbolic, symbolic);

您可以使它們都使用相同的 XSUB 使用這個

    symbolic
    interface_s_ss(arg1, arg2)
	symbolic	arg1
	symbolic	arg2
    INTERFACE:
	multiply divide
	add subtract

(這是 4 個 Perl 函數的完整 XSUB 代碼!) 四個生成的 Perl 函數與相應的 C 函數同名。

這種方法的優點是,與 ALIAS: 關鍵字相比,不需要編寫 switch 语句,每個 Perl 函數(共享相同的 XSUB)都知道應該調用哪個 C 函數。此外,可以通過使用

    CV *mycv = newXSproto("Symbolic::remainder",
			  XS_Symbolic_interface_s_ss, __FILE__, "$$");
    XSINTERFACE_FUNC_SET(mycv, remainder);

從另一個 XSUB,隨時在運行時附加一個額外的函數 remainder()。 (此示例假設不存在 INTERFACE_MACRO: 區段,否則需要使用其他東西代替 XSINTERFACE_FUNC_SET,請參見下一節。)

INTERFACE_MACRO: 關鍵字

此關鍵字允許使用不同的方式來定義 INTERFACE 以提取 XSUB 中的函數指針。跟隨此關鍵字的文本應給出提取函數指針的宏的名稱。提取器宏給出返回類型 CV*,以及 CV*XSANY.any_dptr。設置宏給出 cv 和函數指針。

預設值為XSINTERFACE_FUNCXSINTERFACE_FUNC_SET。如果使用INTERFACE_MACRO關鍵字,則可以省略一個空的函數列表的INTERFACE關鍵字。

假設在前述示例中,multiply()、divide()、add()、subtract()這些函數的指標存儲在全局C數組fp[]中,並且相應的偏移為multiply_offdivide_offadd_offsubtract_off。那麼可以使用以下方式:

    #define XSINTERFACE_FUNC_BYOFFSET(ret,cv,f) \
	((XSINTERFACE_CVT_ANON(ret))fp[CvXSUBANY(cv).any_i32])
    #define XSINTERFACE_FUNC_BYOFFSET_set(cv,f) \
	CvXSUBANY(cv).any_i32 = CAT2( f, _off )

在C部分中,

    symbolic
    interface_s_ss(arg1, arg2)
	symbolic	arg1
	symbolic	arg2
      INTERFACE_MACRO:
	XSINTERFACE_FUNC_BYOFFSET
	XSINTERFACE_FUNC_BYOFFSET_set
      INTERFACE:
	multiply divide
	add subtract

在XSUB部分中。

INCLUDE: 關鍵字

此關鍵字可用於將其他文件包含到XS模塊中。其他文件可能包含XS代碼。INCLUDE: 也可用於運行命令以生成要包含到模塊中的XS代碼。

文件Rpcb1.xsh包含我們的rpcb_gettime()函數

bool_t
rpcb_gettime(host,timep)
      char *host
      time_t &timep
    OUTPUT:
      timep

XS模塊可以使用INCLUDE: 將該文件包含到其中。

INCLUDE: Rpcb1.xsh

如果INCLUDE: 關鍵字的參數後跟一個管道(|),那麼編譯器將將參數解釋為一個命令。這個功能已被INCLUDE_COMMAND: 指令所取代,詳細說明如下。

INCLUDE: cat Rpcb1.xsh |

不要使用它來運行perl: INCLUDE: perl | 將運行位於您路徑中的第一個perl,並不一定是用於運行xsubpp的相同perl。參見"The INCLUDE_COMMAND: Keyword"

INCLUDE_COMMAND: 關鍵字

運行提供的命令並將其輸出包含到當前XS文檔中。 INCLUDE_COMMAND: 將$^X標記賦予特殊含義,即運行與運行xsubpp相同的perl解釋器

INCLUDE_COMMAND: cat Rpcb1.xsh

INCLUDE_COMMAND: $^X -e ...

CASE: 關鍵字

CASE: 關鍵字允許XSUB具有多個不同的部分,每個部分都充當虛擬XSUB。CASE: 是貪婪的,如果使用了它,則所有其他XS關鍵字都必須包含在CASE: 內。這意味著在XSUB中不能有任何東西在第一個CASE: 之前,並且在最後一個CASE: 之後的任何東西都包含在該案例中。

CASE: 可能通過XSUB的參數、通過ix ALIAS: 變量(參見"The ALIAS: Keyword")或者通過items 變量(參見"Variable-length Parameter Lists")進行切換。如果與條件無關聯,最後的CASE: 將成為默認情況。下面的示例顯示了通過ix 切換的CASE:,其中函數rpcb_gettime()具有別名x_gettime()。當以rpcb_gettime() 調用函數時,其參數是通常的(char *host, time_t *timep),但是當以x_gettime() 調用函數時,其參數是反向的,(time_t *timep, char *host)

    long
    rpcb_gettime(a,b)
      CASE: ix == 1
	ALIAS:
	  x_gettime = 1
	INPUT:
	  # 'a' is timep, 'b' is host
          char *b
          time_t a = NO_INIT
        CODE:
               RETVAL = rpcb_gettime( b, &a );
        OUTPUT:
          a
          RETVAL
      CASE:
	  # 'a' is host, 'b' is timep
          char *a
          time_t &b = NO_INIT
        OUTPUT:
          b
          RETVAL

該函數可以使用以下任一語句調用。請注意不同的參數列表。

$status = rpcb_gettime( $host, $timep );

$status = x_gettime( $timep, $host );

EXPORT_XSUB_SYMBOLS: 關鍵字

EXPORT_XSUB_SYMBOLS: 這個關鍵字在早於 5.16.0 版本的 perl 中很可能是你永遠不需要的。在 5.16 版本之前的版本中,這個關鍵字什麼都不做。從 5.16 開始,XSUB 符號不再默認導出。也就是說,它們是 static 函數。如果你在你的 XS 代碼中包含

EXPORT_XSUB_SYMBOLS: ENABLE

這行,該行以下的 XSUB 將不被聲明為 static。你可以稍後使用

EXPORT_XSUB_SYMBOLS: DISABLE

來禁用這個,再次強調,這是你很可能永遠不需要改變的默認值。你不能在 perl 5.16 之前的版本上使用這個關鍵字使 XSUBs 變成 static

單元運算符 &

在 INPUT: 部分中的 & 單元運算符用於告訴 xsubpp 應該使用 & 左邊的 C 類型來將 Perl 值轉換為 C,但在調用 C 函數時提供指向此值的指針。

這對於避免為以引用形式接受參數的 C 函數創建 CODE: 區塊很有用。通常,參數不應該是指針類型(一個 intlong,但不是 int*long*)。

以下的 XSUB 會生成錯誤的 C 代碼。 xsubpp 編譯器將其轉換為調用帶有參數 (char *host, time_t timep) 的代碼,但真正的 rpcb_gettime() 需要 timep 參數是 time_t* 而不是 time_t

bool_t
rpcb_gettime(host,timep)
      char *host
      time_t timep
    OUTPUT:
      timep

這個問題通過使用 & 運算符得到了修正。 xsubpp 編譯器現在將其轉換為正確調用帶有參數 (char *host, time_t *timep) 的代碼。它通過將 & 帶入,使得函數調用看起來像 rpcb_gettime(host, &timep)

bool_t
rpcb_gettime(host,timep)
      char *host
      time_t &timep
    OUTPUT:
      timep

插入 POD、註釋和 C 預處理器指令

在 BOOT:、PREINIT:、INIT:、CODE:、PPCODE:、POSTCALL: 和 CLEANUP: 塊中,以及在函數外部,允許使用 C 預處理器指令。在 MODULE 關鍵字之後的任何位置都允許註釋。編譯器將不受影響地傳遞預處理器指令,並刪除被註釋的行。POD 文檔可以在任何時候出現,包括在 C 和 XS 語言部分。POD 必須以 =cut 命令結束;如果未結束,xsubpp 將退出並顯示錯誤。人生成的 C 代碼很難被誤認為 POD,因為大多數縮排風格導致任何以 = 開頭的行前面都有空格。機器生成的 XS 文件可能會陷入此陷阱,除非注意確保空格中斷序列 "\n="。

可以通過將 # 放在行的第一個非空格位置來向 XSUB 添加註釋。應該注意避免讓註釋看起來像 C 預處理器指令,以免被誤解為這樣的指令。防止這種情況的最簡單方法是在 # 前面加上空格。

如果使用預處理器指令來選擇兩個版本的函數之一,請使用

#if ... version1
#else /* ... version2  */
#endif

而不是

#if ... version1
#endif
#if ... version2
#endif

否則,xsubpp 將認為您重複定義了函數。此外,在 #else/#endif 前放置一個空行,以防它被視為函數主體的一部分。

使用 XS 與 C++

如果 XSUB 名稱包含 ::,則被視為 C++ 方法。生成的 Perl 函數將假定其第一個參數是對象指針。對象指針將存儲在名為 THIS 的變量中。對象應該由 C++ 使用 new() 函數創建,並且應該由 Perl 使用 sv_setref_pv() 宏進行祝福。 Perl 對象的祝福可以由 typemap 處理。本節末尾顯示了一個示例 typemap。

如果 XSUB 的返回類型包含 static,則該方法被認為是靜態方法。它將使用 class::method() 語法調用 C++ 函數。如果該方法不是靜態的,則將使用 THIS->method() 語法調用該函數。

下面的示例將使用以下 C++ 類。

class color {
     public:
     color();
     ~color();
     int blue();
     void set_blue( int );

     private:
     int c_blue;
};

對於 blue() 和 set_blue() 方法,XSUBs 是使用類名定義的,但對象的參數 (THIS 或 "self") 是隱式的,不列出。

int
color::blue()

void
color::set_blue( val )
     int val

兩個 Perl 函數將期望對象作為第一個參數。在生成的 C++ 代碼中,對象被稱為 THIS,並且將在此對象上執行方法調用。因此,在 C++ 代碼中,blue() 和 set_blue() 方法將被調用為

RETVAL = THIS->blue();

THIS->set_blue( val );

您也可以使用可選參數編寫單個 get/set 方法

int
color::blue( val = NO_INIT )
    int val
    PROTOTYPE $;$
    CODE:
        if (items > 1)
            THIS->set_blue( val );
        RETVAL = THIS->blue();
    OUTPUT:
        RETVAL

如果函數名稱是 DESTROY,則將調用 C++ 的 delete 函數,並將 THIS 作為其參數。對於

void
color::DESTROY()

生成的 C++ 代碼將如下所示

color *THIS = ...;  // Initialized as in typemap

delete THIS;

如果函數的名稱是 new,則將調用 C++ 的 new 函數來創建動態的 C++ 物件。 XSUB 會期望給定類名,該類名將保存在名為 CLASS 的變量中,並作為第一個參數。

color *
color::new()

生成的 C++ 代碼將調用 new

RETVAL = new color();

以下是可用於此 C++ 示例的類型映射的示例。

TYPEMAP
color *  O_OBJECT

OUTPUT
# The Perl object is blessed into 'CLASS', which should be a
# char* having the name of the package for the blessing.
O_OBJECT
    sv_setref_pv( $arg, CLASS, (void*)$var );

INPUT
O_OBJECT
    if( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) )
        $var = ($type)SvIV((SV*)SvRV( $arg ));
    else{
        warn(\"${Package}::$func_name() -- \"
            \"$var is not a blessed SV reference\");
        XSRETURN_UNDEF;
    }

接口策略

在設計 Perl 和 C 库之間的接口時,從 C 到 XS 的直接翻譯(如由 h2xs -x 創建的)通常就足夠了。然而,有時接口會看起來非常類似 C,並且偶爾不直觀,特別是當 C 函數修改其參數之一,或者以內部方式返回失敗時(如“負返回值表示失敗”)。在程序員希望創建更符合 Perl 風格的接口時,以下策略可能有助於識別接口的更關鍵部分。

識別具有輸入/輸出或輸出參數的 C 函數。這些函數的 XSUB 可能能夠返回列表給 Perl。

識別使用內部信息作為失敗指示的 C 函數。如果失敗可以在不調用 C 函數的情況下檢測到,您可能希望使用 INIT: 部分來報告失敗。對於在 C 函數返回後可以檢測到的失敗,可能希望使用 POSTCALL: 部分來處理失敗。在更複雜的情況下使用 CODE: 或 PPCODE: 部分。

如果許多函數使用相同的失敗指示基於返回值,您可能希望創建一個特殊的 typedef 來處理此情況。在 XS 文件的開頭附近放置

typedef int negative_is_failure;

並為 negative_is_failure 創建一個 OUTPUT 类型映射條目,將負值轉換為 undef,或者可能是 croak()s。之後,類型為 negative_is_failure 的返回值將創建更符合 Perl 風格的接口。

識別僅由 C 和 XSUB 函數本身使用的值,例如,當函數的參數應該是全局變量的內容時。如果 Perl 不需要訪問值的內容,則可能無需從 C 到 Perl 提供該值的轉換。

識別 C 函數參數列表和返回值中的指針。一些指針可能用於實現輸入/輸出或輸出參數,可以在 XS 中使用 & 一元運算符進行處理,可能還可以使用 NO_INIT 關鍵字。其他一些將需要處理類型,如 int *,並且需要決定在這種情況下有用的 Perl 轉換將做什麼。當語義清楚時,建議將翻譯放入 typemap 文件中。

識別 C 函數使用的結構。在許多情況下,使用 T_PTROBJ typemap 可以很有幫助,這樣這些結構就可以被 Perl 作為 blessed 對象進行操作。(這是由 h2xs -x 自動處理的。)

如果同一個 C 類型在幾個不同的上下文中使用,這些上下文需要不同的翻譯,則 typedef 幾個新類型對應到這個 C 類型,並為這些新類型創建獨立的 typemap 條目。在 XSUBs 的返回類型和參數聲明中使用這些類型。

Perl 對象與 C 結構

處理 C 結構時,應該選擇 T_PTROBJT_PTRREF 作為 XS 類型。這兩種類型都設計用於處理指向複雜對象的指針。T_PTRREF 類型將允許 Perl 對象未被 bless,而 T_PTROBJ 類型要求對象被 bless。通過使用 T_PTROBJ,可以實現一種形式的類型檢查,因為 XSUB 將嘗試驗證 Perl 對象是否為期望的類型。

以下是使用 ONC+ TIRPC 的 getnetconfigent() 函數的 XS 代碼示例。getnetconfigent() 函數將返回指向 C 結構的指針,其 C 原型如下所示。該示例將演示如何將 C 指針轉換為 Perl 引用。Perl 將認為此引用是指向 bless 對象的指針,並將嘗試調用對象的解構函數。XS 源中將提供一個解構函數來釋放 getnetconfigent() 使用的內存。XS 解構函數可以通過指定以單詞 DESTROY 結尾的 XSUB 函數來創建。XS 解構函數可用於釋放可能由另一個 XSUB malloc 的內存。

struct netconfig *getnetconfigent(const char *netid);

將為 struct netconfig 創建一個 typedef。Perl 對象將在與 C 類型名稱匹配的類中被 bless,並附加標籤 Ptr,如果它將是 Perl 包名,則名稱不應該包含嵌入式空格。解構函數將放置在與對象類型相對應的類中,並使用 PREFIX 關鍵字將名稱修剪為單詞 DESTROY,因為 Perl 將期望這樣做。

typedef struct netconfig Netconfig;

MODULE = RPC  PACKAGE = RPC

Netconfig *
getnetconfigent(netid)
     char *netid

MODULE = RPC  PACKAGE = NetconfigPtr  PREFIX = rpcb_

void
rpcb_DESTROY(netconf)
     Netconfig *netconf
   CODE:
     printf("Now in NetconfigPtr::DESTROY\n");
     free( netconf );

此示例需要以下类型映射条目。请参阅perlxstypemap了解有关为扩展添加新类型映射的更多信息。

TYPEMAP
Netconfig *  T_PTROBJ

此示例将与以下Perl语句一起使用。

use RPC;
$netconf = getnetconfigent("udp");

当Perl销毁由$netconf引用的对象时,它将将对象发送到提供的XSUB DESTROY函数。 Perl无法确定,也不关心,该对象是C结构而不是Perl对象。从这个意义上说,由getnetconfigent() XSUB创建的对象与由普通Perl子例程创建的对象没有区别。

在XS中安全存储静态数据

从Perl 5.8开始,已定义了一个宏框架,允许在将从多线程Perl访问的XS模块中安全存储静态数据。

虽然主要设计用于与多线程Perl一起使用,但这些宏已经设计成可以与非线程Perl一起工作。

因此,强烈建议所有使用静态数据的XS模块都使用这些宏。

获取一组模板宏的最简单方法是通过使用h2xs的-g (--global)选项(请参阅h2xs)。

以下是使用这些宏的示例模块。

    #define PERL_NO_GET_CONTEXT
    #include "EXTERN.h"
    #include "perl.h"
    #include "XSUB.h"

    /* Global Data */

    #define MY_CXT_KEY "BlindMice::_guts" XS_VERSION

    typedef struct {
        int count;
        char name[3][100];
    } my_cxt_t;

    START_MY_CXT

    MODULE = BlindMice           PACKAGE = BlindMice

    BOOT:
    {
        MY_CXT_INIT;
        MY_CXT.count = 0;
        strcpy(MY_CXT.name[0], "None");
        strcpy(MY_CXT.name[1], "None");
        strcpy(MY_CXT.name[2], "None");
    }

    int
    newMouse(char * name)
        PREINIT:
          dMY_CXT;
        CODE:
          if (MY_CXT.count >= 3) {
              warn("Already have 3 blind mice");
              RETVAL = 0;
          }
          else {
              RETVAL = ++ MY_CXT.count;
              strcpy(MY_CXT.name[MY_CXT.count - 1], name);
          }
        OUTPUT:
          RETVAL

    char *
    get_mouse_name(index)
          int index
        PREINIT:
          dMY_CXT;
        CODE:
          if (index > MY_CXT.count)
            croak("There are only 3 blind mice.");
          else
            RETVAL = MY_CXT.name[index - 1];
        OUTPUT:
          RETVAL

    void
    CLONE(...)
	CODE:
	  MY_CXT_CLONE;

MY_CXT引用

MY_CXT_KEY

此宏用于定义一个唯一的键,用于引用XS模块的静态数据。建议的命名方案(由h2xs使用)是使用由模块名称、字符串"::_guts"和模块版本号组成的字符串。

#define MY_CXT_KEY "MyModule::_guts" XS_VERSION
typedef my_cxt_t

此结构typedef必须始终称为my_cxt_t。其他CXT*宏假定my_cxt_ttypedef名称存在。

声明一个名为my_cxt_t的typedef,它是一个包含需要在解释器本地存储的所有数据的结构。

typedef struct {
    int some_value;
} my_cxt_t;
START_MY_CXT

始终将START_MY_CXT宏直接放在my_cxt_t的声明之后。

MY_CXT_INIT

MY_CXT_INIT宏初始化my_cxt_t结构的存储。

必须被调用一次,通常在BOOT:部分中。如果您正在维护多个解释器,则应在每个解释器实例中调用它一次,除了从现有解释器克隆的解释器。(但请参阅下面的"MY_CXT_CLONE"。)

dMY_CXT

在所有訪問 MY_CXT 的函數中使用 dMY_CXT 宏(一個聲明)。

MY_CXT

使用 MY_CXT 宏來訪問 my_cxt_t 結構的成員。例如,如果 my_cxt_t

typedef struct {
    int index;
} my_cxt_t;

那麼使用這個來訪問 index 成員

dMY_CXT;
MY_CXT.index = 2;
aMY_CXT/pMY_CXT

dMY_CXT 可能計算成本很高,為了避免在每個函數中調用它的開銷,可以使用 aMY_CXT/pMY_CXT 宏將聲明傳遞給其他函數,例如

    void sub1() {
	dMY_CXT;
	MY_CXT.index = 1;
	sub2(aMY_CXT);
    }

    void sub2(pMY_CXT) {
	MY_CXT.index = 2;
    }

類似於 pTHX,當宏是多個參數中的第一個或最後一個時,也有等效形式,其中底線代表逗號,即 _aMY_CXTaMY_CXT__pMY_CXTpMY_CXT_

MY_CXT_CLONE

默認情況下,當新建解譯器作為現有解譯器的副本(例如通過 threads->create())時,兩個解譯器共享相同的物理 my_cxt_t 結構。調用 MY_CXT_CLONE(通常通過包的 CLONE() 函數),導致結構的逐字節複製,並且以後的 dMY_CXT 將導致訪問複本而不是原來的結構。

MY_CXT_INIT_INTERP(my_perl)
dMY_CXT_INTERP(my_perl)

這些是接受顯式解譯器作為參數的宏的版本。

請注意,這些宏只能在同一個源文件中一起工作;也就是說,一個源文件中的 dMY_CTX 將訪問不同的結構,而另一個源文件中的 dMY_CTX 將訪問不同的結構。

Thread-aware system interfaces

從 Perl 5.8 開始,在 C/C++ 級別上 Perl 知道如何將具有支持多線程版本的系統/庫接口(例如 getpwent_r())包裝到前端宏(例如 getpwent())中,以正確處理與 Perl 解譯器的多線程交互。這將自動發生,您唯一需要做的就是實例化 Perl 解譯器。

在編譯 Perl 核心原始碼(定義了 PERL_CORE)或 Perl 核心擴展(定義了 PERL_EXT)時,始終會發生這種包裹。當在 Perl 核心之外編譯 XS 代碼時,Perl 5.28 之前不會發生包裹。從那個版本開始,您可以

#define PERL_REENTRANT

在您的代碼中啟用包裹。如果您使用這些函數,建議這樣做,因為混合使用帶有 _r 表示的形式(如編譯為多線程操作的 Perl 將進行的操作)和不帶 _r 表示的形式既不是很好定義的(結果不一致,數據損壞,甚至崩潰變得更有可能),也不是非常可移植的。不幸的是,並非所有系統都具有所有 _r 形式,但使用此 #define 為您提供了 Perl 在每個系統上已知的可用保護。

範例

文件 RPC.xs:與某些 ONC+ RPC 綁定庫函數的接口。

     #define PERL_NO_GET_CONTEXT
     #include "EXTERN.h"
     #include "perl.h"
     #include "XSUB.h"

     /* Note: On glibc 2.13 and earlier, this needs be <rpc/rpc.h> */
     #include <tirpc/rpc.h>

     typedef struct netconfig Netconfig;

     MODULE = RPC  PACKAGE = RPC

     SV *
     rpcb_gettime(host="localhost")
          char *host
	PREINIT:
          time_t  timep;
        CODE:
          ST(0) = sv_newmortal();
          if( rpcb_gettime( host, &timep ) )
               sv_setnv( ST(0), (double)timep );

     Netconfig *
     getnetconfigent(netid="udp")
          char *netid

     MODULE = RPC  PACKAGE = NetconfigPtr  PREFIX = rpcb_

     void
     rpcb_DESTROY(netconf)
          Netconfig *netconf
        CODE:
          printf("NetconfigPtr::DESTROY\n");
          free( netconf );

文件 typemap:RPC.xs 的自定義類型映射。(參見 perlxstypemap

TYPEMAP
Netconfig *  T_PTROBJ

文件 RPC.pm:RPC 擴展的 Perl 模塊。

package RPC;

require Exporter;
require DynaLoader;
@ISA = qw(Exporter DynaLoader);
@EXPORT = qw(rpcb_gettime getnetconfigent);

bootstrap RPC;
1;

文件 rpctest.pl:RPC 擴展的 Perl 測試程序。

use RPC;

$netconf = getnetconfigent();
$a = rpcb_gettime();
print "time = $a\n";
print "netconf = $netconf\n";

$netconf = getnetconfigent("tcp");
$a = rpcb_gettime("poplar");
print "time = $a\n";
print "netconf = $netconf\n";

在 Makefile.PL 中添加 -ltirpc 和 -I/usr/include/tirpc。

注意事項

XS 代碼具有對系統調用的完全訪問權限,包括 C 函數庫。因此,它具有干擾 Perl 核心或其他模塊設置的事物的能力,例如信號處理程序或文件處理程序。它可能會搞亂記憶體,或者任何許多有害的事情。不要這樣做。

一些模塊具有事件循環,等待用戶輸入。兩個這樣的模塊在單個 Perl 應用程序中一起工作的可能性非常小。

一般來說,perl 解釋器將自己視為 Perl 程序的宇宙中心。XS 代碼被視為一個助手,用於完成 perl 不執行或執行不夠快的任務,但始終是 perl 的下級。XS 代碼越接近這個模型,衝突的可能性就越小。

有一個有爭議的領域是關於C語言區域設定。 (請參閱 perllocale。)Perl 在一個例外情況下,除非另有說明,否則將程式運行所在的底層區域設定為從環境中傳入的區域設定。這與一般的C語言程式有重要的不同,一般的C語言程式的底層區域設定是“C”區域,除非程式對其進行更改。 從v5.20開始,除了在use locale的詞法範圍外,這個底層區域設定對於純Perl程式碼完全隱藏,除了在POSIX模塊中的一些必要的函數呼叫會使用它。但是,底層區域設定,除了一個例外,暴露給了XS代碼,影響了所有行為與區域設定有關的C庫例程。您的XS代碼最好不要假設底層區域設定是“C”。例外是LC_NUMERIC區域類別,它之所以是一個例外是因為經驗表明它對XS代碼可能會有問題,而對於其他區域類別我們沒有收到問題報告。這個問題的原因是這個類別中使用作為小數點的字符可能會有所不同。許多歐洲語言使用逗號,而英語,因此Perl期望一個點(U+002E:句號)。許多模塊只能處理基數字符為點,因此perl試圖使其成為這樣。直到Perl v5.20,該嘗試僅僅是在啟動時將LC_NUMERIC設置為"C"區域。否則的話,任何setlocale()會導致更改;這導致了一些失敗。因此,從v5.22開始,perl嘗試始終將LC_NUMERIC設置為"C"區域,以供XS代碼使用。

總結一下,在XS代碼中應該預期什麼並如何處理區域設定

不具區域設定意識的XS代碼

請記住,即使您認為您的代碼不具有區域設定意識,它可能會調用一個具有此意識的庫函數。希望這種函數的手冊頁會指出這種依賴關係,但文件可能不完善。

當前的區域設定對XS代碼是可見的,除了可能的LC_NUMERIC(下一段中有解釋)。其他類別尚未收到問題報告。Perl在啟動時初始化設置,以使當前的區域設定是由用戶在該時刻生效的環境中指示的區域設定。參見"perllocale"中的“ENVIRONMENT”

然而,直到 v5.20,Perl 在啟動時會初始化一些東西,以便將 LC_NUMERIC 設置為 "C" 區域設置。但是,如果任何地方的代碼更改了它,它將保持更改。這意味著您的模塊不能指望 LC_NUMERIC 是特定的某個值,您也不能期望浮點數(包括版本字符串)中有點。如果您不允許出現非點,則如果任何地方的任何人更改了區域設置,您的代碼可能會中斷。出於這個原因,v5.22 改變了行為,以便 Perl 嘗試保持 LC_NUMERIC 在 "C" 區域設置,除了內部應該是其他的操作。行為不良的 XS 代碼始終可以更改區域設置,但最常見的情況已被檢查並處理。

具有區域感知的 XS 代碼

如果需要從用戶環境中獲取區域設置,則除了 LC_NUMERIC 外,XS 代碼不應該設置區域設置,因為 perl 已經設置了其他的。XS 代碼應該避免更改區域設置,因為這可能會對其他不相關的代碼產生不利影響,並且可能不是線程安全的。為了最小化問題,應該使用宏 "perlapi 中的 STORE_LC_NUMERIC_SET_TO_NEEDED""perlapi 中的 STORE_LC_NUMERIC_FORCE_TO_UNDERLYING""perlapi 中的 RESTORE_LC_NUMERIC" 來影響任何需要的更改。

但是,從 Perl v5.28 開始,在支持此功能的平台上,區域設置是線程安全的。Windows 從 Visual Studio 2005 開始支持此功能。許多其他現代平台支持線程安全的 POSIX 2008 函數。如果此版本使用了這些功能,則 C 中的 #define USE_THREAD_SAFE_LOCALE 將被定義。從 Perl 空間,只讀變量 ${SAFE_LOCALES} 如果構建未線程化,或者如果定義了 USE_THREAD_SAFE_LOCALE,則為 1;否則為 0。

底層工作方式是每個執行緒都可以選擇使用特定於其的語言環境(這是Windows和POSIX 2008的功能),或是全域語言環境,可供所有執行緒存取(這是一直存在的功能)。Windows和POSIX的實現方式完全不同。在Windows上,運行時可以設置標準 setlocale(3) 函數僅知道全域語言環境或此執行緒的語言環境。在POSIX上,setlocale 始終處理全域語言環境,並創建其他函數來處理每個執行緒的語言環境。Perl使這對perl空間代碼透明。它繼續使用 POSIX::setlocale(),並將其轉換為每個執行緒函數。

所有其他與語言環境敏感的函數都會自動使用每個執行緒的語言環境(如果已啟用),如果未啟用,則使用全域語言環境。因此,對於使用每個執行緒語言環境的POSIX系統,對 setlocale 的調用對當前執行緒無效。如果Perl編譯為單線程操作,它不會使用每個執行緒函數,因此 setlocale 的行為如預期般正常。

如果已加載 POSIX 模組,您可以使用 perlcall 中提供的方法調用 POSIX::setlocale 以安全地更改或查詢語言環境(在安全的系統上),或者您可以改用新的5.28版本函數 perlapi中的"Perl_setlocale",它是系統 setlocale(3) 的一個可插入替換,並且能夠透明地處理單線程和多線程應用程序。

仍然存在一些與語言環境相關的庫調用,因為它們返回的數據位於全域所有執行緒共享的緩衝區中,所以它們並不是線程安全的。過去這並不重要,因為語言環境本身並不是線程安全的。但現在如果您的模塊在多線程應用程序中被調用,則必須注意這一點。已知的問題如下:

asctime()
ctime()
gcvt() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
getdate()
wcrtomb() if its final argument is NULL
wcsrtombs() if its final argument is NULL
wcstombs()
wctomb()

其中一些實際上不應該在Perl應用程序中調用,對於其他一些,已經實現了線程安全版本。

asctime_r()
ctime_r()
Perl_langinfo()

如果您在Perl 5.28中編譯代碼,則會自動使用帶有 _r 的形式。

#define PERL_REENTRANT

另請參見 perlapi中的"Perl_langinfo"。您可以使用 perlcall 中提供的方法來獲取這些的最佳可用的語言環境安全版本。

POSIX::localeconv()
POSIX::wcstombs()
POSIX::wctomb()

另外請注意,Localeconv 返回的某些項目可以通過 perlapi中的"Perl_langinfo" 獲取。

其他函數不應在多線程應用程序中使用。

有些模組可能會呼叫一個支援區域設定的非 Perl 函式庫。只要不嘗試使用系統的 setlocale 查詢或更改區域設定,這是沒有問題的。但是如果這些模組呼叫了系統的 setlocale,這些呼叫可能無效。相反,Perl_setlocale 在所有情況下都有效。在多線程 POSIX 2008 系統上,普通的 setlocale 是無效的。它僅對全局區域設定進行操作,而每個線程都有自己的區域設定,不關心全局區域設定。由於將這些非 Perl 函式庫轉換為 Perl_setlocale 是不可能的,所以在 v5.28 中引入了一個新的函數 switch_to_global_locale,該函數將調用它的線程切換到全局區域設定,這樣任何系統的 setlocale 調用都會產生預期的效果。在返回 perl 之前必須調用函數 sync_locale

這個線程可以隨意更改區域設定,而不會影響到其他線程,除非其他線程也已切換到全局區域設定。這意味著多線程應用程序可以使用一個線程來使用外部函式庫而不會出現問題;但是不能有超過一個線程這樣使用。可能會產生不良結果。

在不支援多線程區域設定的 perl 中,某些外部函式庫,如 Gtk,會更改區域設定。這可能對 Perl 核心和其他模組造成問題。對於這些情況,在返回 perl 之前,從 XS 調用函數 sync_locale() 應該足以避免大部分問題。在此之前,您需要一個純 Perl 陳述來實現這一點。

POSIX::setlocale(LC_ALL, POSIX::setlocale(LC_ALL));

或者使用 perlcall 中給出的方法。

XS 版本

本文涵蓋了由 ExtUtils::ParseXS(也稱為 xsubpp)3.51 支援的功能。

作者診斷

從版本 3.49 開始,某些警告默認禁用。在開發過程中,您可以在環境中或在 Makefile.PL 中將 $ENV{AUTHOR_WARNINGS} 設置為 true,或者通過代碼將 $ExtUtils::ParseXS::AUTHOR_WARNINGS 設置為 true,或者在調用 process_file() 時顯式地傳遞 author_warnings=>1。目前,這將啟用更嚴格的別名檢查,但將來可能會添加更多警告。這種警告只對 XS 文件的作者有幫助,產生的診斷信息不包括安裝特定的細節,因此只對 XS 代碼本身的維護者有用。

作者

最初由 Dean Roehrich <roehrich@cray.com> 撰寫。

自 1996 年以來由 Perl Porters <perl5-porters@perl.org> 維護。