目錄

名稱

perlcall - Perl 呼叫慣例來自 C

說明

此文件目的是向您展示如何直接從 C 呼叫 Perl 子常式,亦即如何撰寫回呼

除了討論 Perl 提供給 C 的介面以撰寫回呼之外,此文件使用一系列範例來說明介面實際上如何在實務中運作。此外,還涵蓋了一些編碼回呼的技術。

需要回呼的範例包括

雖然本文所述的技術適用於將 Perl 嵌入在 C 程式中,但這並非本文的主要目標。還有其他必須考慮的細節,而且這些細節是特定於嵌入 Perl 的。有關將 Perl 嵌入在 C 中的詳細資訊,請參閱 perlembed

在您開始閱讀本文的其餘部分之前,建議先閱讀以下兩個文件--perlxsperlguts

CALL_ 函式

雖然使用範例說明這些內容比較容易,但您首先需要了解幾個重要的定義。

Perl 有許多 C 函式,讓您可以呼叫 Perl 子常式。它們是

I32 call_sv(SV* sv, I32 flags);
I32 call_pv(char *subname, I32 flags);
I32 call_method(char *methname, I32 flags);
I32 call_argv(char *subname, I32 flags, char **argv);

關鍵函式是 call_sv。所有其他函式都是相當簡單的包裝函式,它們讓您在特殊情況下更容易呼叫 Perl 子常式。最後,它們都會呼叫 call_sv 來呼叫 Perl 子常式。

所有 call_* 函式都有 flags 參數,用於將選項的位元遮罩傳遞給 Perl。這個位元遮罩對每個函式運作方式相同。位元遮罩中可用的設定會在 "FLAG 值" 中討論。

現在將逐一討論每個函式。

call_sv

call_sv 有兩個參數。第一個參數 sv 是 SV*。這讓您可以指定要呼叫的 Perl 子常式,方式是使用 C 字串(已先轉換為 SV)或對子常式的參照。區段 "使用 call_sv" 顯示如何使用 call_sv

call_pv

函式 call_pv 類似於 call_sv,但它預期其第一個參數是 C char*,用於識別您要呼叫的 Perl 子常式,例如,call_pv("fred", 0)。如果您要呼叫的子常式在另一個套件中,請將套件名稱包含在字串中,例如,"pkg::fred"

call_method

函數 call_method 用於呼叫 Perl 類別中的方法。參數 methname 對應於要呼叫的方法名稱。請注意,方法所屬的類別會傳遞至 Perl 堆疊,而非參數清單中。此類別可以是類別名稱(對於靜態方法)或物件參考(對於虛擬方法)。請參閱 perlobj 以取得有關靜態和虛擬方法的更多資訊,以及 "使用 call_method" 以取得使用 call_method 的範例。

call_argv

call_argv 呼叫由儲存在 subname 參數中的 C 字串所指定的 Perl 子常式。它也採用一般的 flags 參數。最後一個參數 argv 包含一個以 NULL 結尾的 C 字串清單,作為傳遞至 Perl 子常式的參數。請參閱 "使用 call_argv"

所有函數都會傳回一個整數。這是 Perl 子常式傳回的項目數目。子常式實際傳回的項目會儲存在 Perl 堆疊中。

一般而言,您應該永遠檢查這些函數的傳回值。即使您預期 Perl 子常式只會傳回特定數目的值,但沒有任何機制可以阻止其他人執行意外的動作 - 不要說您沒有被警告。

FLAG 值

所有 call_* 函數中的 flags 參數是 G_VOIDG_SCALARG_LIST 之一,用於指出呼叫內容,並與下面定義的其他 G_* 符號的位元遮罩進行 OR 運算。

G_VOID

在 void 內容中呼叫 Perl 子常式。

此旗標有 2 個效果

  1. 它會向被呼叫的子常式指出它在 void 內容中執行(如果它執行 wantarray,結果將會是未定義的值)。

  2. 它會確保子常式實際上不會傳回任何內容。

call_* 函數傳回的值指出 Perl 子常式已傳回多少個項目 - 在此情況下,它將會是 0。

G_SCALAR

在標量內容中呼叫 Perl 子常式。這是所有 call_* 函數的預設內容旗標設定。

此旗標有 2 個效果

  1. 它會向被呼叫的子常式指出它在標量內容中執行(如果它執行 wantarray,結果將會是 false)。

  2. 它會確保子常式實際上只傳回一個標量。當然,子常式可以忽略 wantarray 並傳回一個清單。如果是這樣,則只會傳回清單的最後一個元素。

call_* 函數傳回的值表示 Perl 子常式傳回的項目數目 - 在此情況下,它將是 0 或 1。

如果為 0,則表示您已指定 G_DISCARD 旗標。

如果為 1,則 Perl 子常式實際傳回的項目將儲存在 Perl 堆疊中 - "傳回純量" 區段顯示如何存取堆疊中的這個值。請記住,不論 Perl 子常式傳回多少項目,只有最後一個項目可以從堆疊中存取 - 想像只有一個值傳回的情況,就像只有一個元素的清單。在 call_* 函數傳回控制權時,其他傳回的項目將不存在。"在純量內容中傳回清單" 區段顯示這個行為的範例。

G_LIST

在清單內容中呼叫 Perl 子常式。在 Perl 版本 5.35.1 之前,這稱為 G_ARRAY

與 G_SCALAR 一樣,這個旗標有 2 個效果

  1. 它表示被呼叫的子常式在清單內容中執行 (如果執行 wantarray,結果將為 true)。

  2. 它確保從子常式傳回的所有項目在 call_* 函數傳回控制權時都可以存取。

call_* 函數傳回的值表示 Perl 子常式傳回的項目數目。

如果為 0,則表示您已指定 G_DISCARD 旗標。

如果不為 0,則它將是子常式傳回的項目數目。這些項目將儲存在 Perl 堆疊中。"傳回值清單" 區段提供使用 G_LIST 旗標和從 Perl 堆疊存取傳回項目的機制的範例。

G_DISCARD

預設情況下,call_* 函數會將 Perl 子常式傳回的項目置於堆疊中。如果您對這些項目不感興趣,則設定這個旗標將使 Perl 自動為您清除這些項目。請注意,您仍然可以使用 G_SCALAR 或 G_LIST 來表示 Perl 子常式的內容。

如果您未設定這個旗標,則非常 重要的是,您必須確保自己處理任何暫存項目 (即,傳遞給 Perl 子常式的參數和從子常式傳回的值)。"傳回純量" 區段提供如何明確處理這些暫存項目的詳細資訊,而 "使用 Perl 處理暫存項目" 區段則討論您可以在哪些特定情況下忽略問題,並讓 Perl 為您處理。

G_NOARGS

每當使用其中一個 call_* 函數呼叫 Perl 子常式時,預設會假設要將參數傳遞給子常式。如果您沒有傳遞任何參數給 Perl 子常式,您可以透過設定此標記來節省一些時間。此標記的效果是不會為 Perl 子常式建立 @_ 陣列。

雖然此標記提供的功能看起來很直接,但只有在有充分理由時才應該使用。之所以要小心,是因為即使您已指定 G_NOARGS 標記,被呼叫的 Perl 子常式仍有可能認為您已傳遞參數給它。

事實上,可能會發生 Perl 子常式可以從先前的 Perl 子常式存取 @_ 陣列的情況。當執行 call_* 函數的程式碼本身已從另一個 Perl 子常式呼叫時,就會發生這種情況。以下程式碼說明了這一點

sub fred
  { print "@_\n"  }

sub joe
  { &fred }

&joe(1,2,3);

這將會列印

1 2 3

發生的事情是 fred 存取屬於 joe@_ 陣列。

G_EVAL

您呼叫的 Perl 子常式有可能會異常終止,例如,透過明確呼叫 die 或實際上不存在。預設情況下,當發生這些事件時,處理程序將立即終止。如果您想要捕捉這種類型的事件,請指定 G_EVAL 標記。它會在子常式呼叫周圍加上 eval { }

每當控制權從 call_* 函數傳回時,您需要檢查 $@ 變數,就像在一般的 Perl 程式碼中一樣。

call_* 函數傳回的值取決於已指定哪些其他標記,以及是否發生錯誤。以下是可能發生的所有不同情況

有關使用 G_EVAL 的詳細資訊,請參閱 "使用 G_EVAL"

G_KEEPERR

使用上面所述的 G_EVAL 標記將永遠設定 $@:如果沒有錯誤,則清除它;如果呼叫的程式碼中有錯誤,則設定它來描述錯誤。如果您打算處理可能的錯誤,這就是您想要的,但有時您只想捕捉錯誤並阻止它們干擾程式的其他部分。

此情境大多適用於從解構函數、非同步回呼和訊號處理常式中呼叫的程式碼。在這種情況下,被呼叫的程式碼與周圍的動態內容關聯性很低,主程式需要與被呼叫的程式碼中的錯誤隔離,即使無法以智慧的方式處理這些錯誤。這也可能用於 __DIE____WARN__ 鉤子和 tie 函數的程式碼。

G_KEEPERR 旗標用於與 G_EVAL 搭配使用,用於實作此類程式碼的 call_* 函數,或與 eval_sv 搭配使用。當未搭配 G_EVAL 使用時,此旗標不會對 call_* 函數產生任何影響。

當使用 G_KEEPERR 時,被呼叫的程式碼中的任何錯誤都會像往常一樣終止呼叫,而且錯誤不會傳播到呼叫之外(這對 G_EVAL 來說很常見),但它不會進入 $@。相反地,錯誤將轉換為警告,並加上字串「\t(在清理中)」作為前綴。這可以使用 no warnings 'misc' 停用。如果沒有錯誤,則不會清除 $@

請注意,G_KEEPERR 旗標不會傳播到內部 eval;這些 eval 仍可能設定 $@

G_KEEPERR 旗標是在 Perl 版本 5.002 中引入的。

請參閱 「使用 G_KEEPERR」,以取得需要使用此旗標的情況範例。

判斷內容

如上所述,您可以使用 wantarray 判斷 Perl 中目前正在執行的子常式的內容。可以在 C 中使用 GIMME_V 巨集來進行等效測試,如果在清單內容中呼叫您,則它會傳回 G_LIST,如果在純量內容中呼叫您,則會傳回 G_SCALAR,如果在空內容中呼叫您,則會傳回 G_VOID(亦即,不會使用傳回值)。此巨集的舊版本稱為 GIMME;在空內容中,它會傳回 G_SCALAR,而不是 G_VOID。在區段 「使用 GIMME_V」 中顯示了使用 GIMME_V 巨集的範例。

範例

定義的討論已經夠多了!我們來看幾個範例。

Perl 提供許多巨集來協助存取 Perl 堆疊。在與 Perl 內部介面時,應盡可能始終使用這些巨集。我們希望這能讓程式碼較不容易受到未來對 Perl 所做的任何變更影響。

另一個值得注意的重點是,在第一系列範例中,我只使用了 call_pv 函數。這樣做是為了讓程式碼更簡單,並讓您更容易了解主題。在可能的情況下,如果要在 call_pvcall_sv 之間做選擇,您應該始終嘗試使用 call_sv。請參閱 「使用 call_sv」 以取得詳細資訊。

無參數,無傳回值

這個第一個簡單範例將呼叫 Perl 子常式 PrintUID,以列印出程序的 UID。

sub PrintUID
{
    print "UID is $<\n";
}

以下是呼叫它的 C 函數

static void
call_PrintUID()
{
    dSP;

    PUSHMARK(SP);
    call_pv("PrintUID", G_DISCARD|G_NOARGS);
}

簡單吧?

有關此範例的幾點注意事項

  1. 目前請忽略 dSPPUSHMARK(SP)。它們將在下一範例中討論。

  2. 我們沒有傳遞任何參數給 PrintUID,因此可以指定 G_NOARGS。

  3. 我們對 PrintUID 回傳的任何內容不感興趣,因此指定 G_DISCARD。即使 PrintUID 已變更為回傳一些值,指定 G_DISCARD 表示當控制權從 call_pv 回傳時,這些值將會被清除。

  4. 由於正在使用 call_pv,因此 Perl 子常式指定為 C 字串。在此情況下,子常式名稱已「硬式編碼」到程式碼中。

  5. 因為我們指定 G_DISCARD,因此不需要檢查從 call_pv 回傳的值。它永遠都會是 0。

傳遞參數

現在讓我們建立一個稍微複雜一點的範例。這次我們想要呼叫 Perl 子常式 LeftString,它會採用 2 個參數--字串 ($s) 和整數 ($n)。子常式只會列印字串的前 $n 個字元。

因此 Perl 子常式會如下所示

sub LeftString
{
    my($s, $n) = @_;
    print substr($s, 0, $n), "\n";
}

呼叫 LeftString 所需的 C 函式會如下所示

    static void
    call_LeftString(a, b)
    char * a;
    int b;
    {
        dSP;

	ENTER;
        SAVETMPS;

        PUSHMARK(SP);
        EXTEND(SP, 2);
        PUSHs(sv_2mortal(newSVpv(a, 0)));
        PUSHs(sv_2mortal(newSViv(b)));
        PUTBACK;

        call_pv("LeftString", G_DISCARD);

        FREETMPS;
        LEAVE;
    }

以下是關於 C 函式 call_LeftString 的一些注意事項。

  1. 參數使用 Perl 堆疊傳遞到 Perl 子常式。這是從 dSP 行開始到 PUTBACK 行結束的程式碼的目的。dSP 宣告堆疊指標的區域副本。此區域副本永遠都應該存取為 SP

  2. 如果您要將某些內容放入 Perl 堆疊中,您需要知道要將其放入何處。這是巨集 dSP 的目的--它宣告和初始化 Perl 堆疊指標的區域副本。

    此範例中將使用的所有其他巨集都需要您使用此巨集。

    此規則的例外情況是,如果您從 XSUB 函式直接呼叫 Perl 子常式。在此情況下,不需要明確使用 dSP 巨集--它會自動為您宣告。

  3. 任何要推入堆疊的參數都應該用 PUSHMARKPUTBACK 巨集括起來。在此背景下,這兩個巨集的目的是自動計算您推入的參數數量。然後,每當 Perl 為子常式建立 @_ 陣列時,它就會知道要建立多大。

    PUSHMARK 巨集會指示 Perl 記下目前堆疊指標。即使您沒有傳遞任何參數(例如在 "No Parameters, Nothing Returned" 區段中所示的範例),您仍然必須在呼叫任何 call_* 函式之前呼叫 PUSHMARK 巨集--Perl 仍然需要知道沒有參數。

    PUTBACK 巨集會將堆疊指標的全球副本設定為與我們的區域副本相同。如果我們沒有這麼做,call_pv 就不知道我們推入的兩個參數在哪裡--請記住,到目前為止我們所做的所有堆疊指標處理都是使用我們的區域副本,而不是 全球副本。

  4. 接下來,我們會看到 EXTEND 和 PUSH。這是參數實際推入堆疊的地方。在此情況下,我們會推入字串和整數。

    或者,您可以使用 XPUSHs() 巨集,它會結合 EXTEND(SP, 1)PUSHs()。如果您要推入多個值,這會比較沒效率。

    有關 PUSH 巨集如何運作的詳細資訊,請參閱 perlguts 中的「XSUB 和引數堆疊」

  5. 由於我們建立了暫時值(透過 sv_2mortal() 呼叫),我們必須整理 Perl 堆疊並處置暫時 SV。

    這是

    ENTER;
    SAVETMPS;

    在函式開頭,以及

    FREETMPS;
    LEAVE;

    在結尾的目的。ENTER/SAVETMPS 配對為我們建立的任何暫時值建立界限。這表示我們要擺脫的暫時值將會限制在這些呼叫之後建立的暫時值。

    FREETMPS/LEAVE 配對將會擺脫 Perl 子常式傳回的任何值(請參閱下一個範例),而且它也會傾印我們建立的暫時 SV。在程式碼開頭放置 ENTER/SAVETMPS 可確保不會毀損其他暫時值。

    將這些巨集視為在 Perl 中使用 {} 來限制區域變數的範圍。

    有關使用這些巨集的替代方案詳細資訊,請參閱區段 「使用 Perl 處置暫時值」

  6. 最後,現在可以透過 call_pv 函式呼叫 LeftString。這次指定的唯一旗標是 G_DISCARD。由於我們這次傳遞 2 個參數給 Perl 子常式,因此我們沒有指定 G_NOARGS。

傳回純量

現在提供一個處理 Perl 子常式傳回項目的範例。

以下是 Perl 子常式 Adder,它會取得 2 個整數參數並單純傳回它們的總和。

sub Adder
{
    my($a, $b) = @_;
    $a + $b;
}

由於我們現在關注 Adder 的傳回值,因此呼叫它的 C 函式現在會再複雜一點。

static void
call_Adder(a, b)
int a;
int b;
{
    dSP;
    int count;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 2);
    PUSHs(sv_2mortal(newSViv(a)));
    PUSHs(sv_2mortal(newSViv(b)));
    PUTBACK;

    count = call_pv("Adder", G_SCALAR);

    SPAGAIN;

    if (count != 1)
        croak("Big trouble\n");

    printf ("The sum of %d and %d is %d\n", a, b, POPi);

    PUTBACK;
    FREETMPS;
    LEAVE;
}

這次要注意的重點是

  1. 這次指定的唯一旗標是 G_SCALAR。這表示將會建立 @_ 陣列,而且 Adder 傳回的值在呼叫 call_pv 之後仍然會存在。

  2. 巨集 SPAGAIN 的目的是要更新堆疊指標的區域副本。這是必要的,因為在 call_pv 呼叫期間,分配給 Perl 堆疊的記憶體可能會重新分配。

    如果您在程式碼中使用 Perl 堆疊指標,您必須在使用 call_* 函式或任何其他 Perl 內部函式時,使用 SPAGAIN 更新區域副本。

  3. 儘管預期 Adder 只會傳回一個值,但還是建議檢查 call_pv 的傳回碼。

    期待單一值與知道將會有一個值並不完全相同。如果有人修改了Adder以傳回一個清單,而我們沒有檢查該可能性並採取適當的措施,Perl 堆疊將會處於不一致的狀態。那是你真的不希望發生的情況。

  4. POPi 巨集用於在此從堆疊中彈出傳回值。在此情況下,我們想要一個整數,所以使用了 POPi

    以下是可用的 POP 巨集的完整清單,以及它們傳回的類型。

    POPs	SV
    POPp	pointer (PV)
    POPpbytex   pointer to bytes (PV)
    POPn	double (NV)
    POPi	integer (IV)
    POPu        unsigned integer (UV)
    POPl	long
    POPul       unsigned long

    由於這些巨集具有副作用,因此不要將它們用作巨集的引數,這些巨集可能會評估其引數多次,例如

    /* Bad idea, don't do this */
    STRLEN len;
    const char *s = SvPV(POPs, len);

    改用暫時變數

    STRLEN len;
    SV *sv = POPs;
    const char *s = SvPV(sv, len);

    或保證只評估其引數一次的巨集

    STRLEN len;
    const char *s = SvPVx(POPs, len);
  5. 最後的 PUTBACK 用於在離開函式前將 Perl 堆疊保留在一致的狀態。這是必要的,因為當我們使用 POPi 從堆疊中彈出傳回值時,它只更新了堆疊指標的本機副本。請記住,PUTBACK 將全域堆疊指標設定為與本機副本相同。

傳回值清單

現在,讓我們擴充前一個範例以傳回參數的總和和差。

以下是 Perl 子常式

sub AddSubtract
{
   my($a, $b) = @_;
   ($a+$b, $a-$b);
}

這是 C 函式

static void
call_AddSubtract(a, b)
int a;
int b;
{
    dSP;
    int count;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 2);
    PUSHs(sv_2mortal(newSViv(a)));
    PUSHs(sv_2mortal(newSViv(b)));
    PUTBACK;

    count = call_pv("AddSubtract", G_LIST);

    SPAGAIN;

    if (count != 2)
        croak("Big trouble\n");

    printf ("%d - %d = %d\n", a, b, POPi);
    printf ("%d + %d = %d\n", a, b, POPi);

    PUTBACK;
    FREETMPS;
    LEAVE;
}

如果像這樣呼叫 call_AddSubtract

call_AddSubtract(7, 4);

以下是輸出

7 - 4 = 3
7 + 4 = 11

備註

  1. 我們想要清單內容,所以使用了 G_LIST。

  2. 不出所料,這次使用了 POPi 兩次,因為我們從堆疊中擷取了 2 個值。要注意的重要事項是,在使用 POP* 巨集時,它們會以反向順序從堆疊中移除。

在純量內容中傳回清單

假設前一節中的 Perl 子常式是在純量內容中呼叫的,如下所示

static void
call_AddSubScalar(a, b)
int a;
int b;
{
    dSP;
    int count;
    int i;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 2);
    PUSHs(sv_2mortal(newSViv(a)));
    PUSHs(sv_2mortal(newSViv(b)));
    PUTBACK;

    count = call_pv("AddSubtract", G_SCALAR);

    SPAGAIN;

    printf ("Items Returned = %d\n", count);

    for (i = 1; i <= count; ++i)
        printf ("Value %d = %d\n", i, POPi);

    PUTBACK;
    FREETMPS;
    LEAVE;
}

所做的另一個修改是,call_AddSubScalar 將印出從 Perl 子常式傳回的項目數目及其值(為簡化起見,它假設它們是整數)。因此,如果呼叫 call_AddSubScalar

call_AddSubScalar(7, 4);

則輸出將會是

Items Returned = 1
Value 1 = 3

此情況下要注意的主要重點是,只有清單中的最後一個項目會從子常式回傳。AddSubtract 實際上已返回至 call_AddSubScalar

透過參數清單從 Perl 回傳資料

也可以直接透過參數清單回傳值,但實際上是否需要這麼做則是另一回事。

下列 Perl 子常式 Inc 會採用 2 個參數,並直接遞增每個參數。

sub Inc
{
    ++ $_[0];
    ++ $_[1];
}

以下是呼叫它的 C 函式。

    static void
    call_Inc(a, b)
    int a;
    int b;
    {
        dSP;
        int count;
        SV * sva;
        SV * svb;

        ENTER;
        SAVETMPS;

        sva = sv_2mortal(newSViv(a));
        svb = sv_2mortal(newSViv(b));

        PUSHMARK(SP);
        EXTEND(SP, 2);
        PUSHs(sva);
        PUSHs(svb);
        PUTBACK;

        count = call_pv("Inc", G_DISCARD);

        if (count != 0)
            croak ("call_Inc: expected 0 values from 'Inc', got %d\n",
                   count);

        printf ("%d + 1 = %d\n", a, SvIV(sva));
        printf ("%d + 1 = %d\n", b, SvIV(svb));

	FREETMPS;
	LEAVE;
    }

要在 call_pv 回傳後存取推入堆疊中的兩個參數,必須記下其位址,因此有了兩個變數 svasvb

之所以需要這麼做,是因為 Perl 堆疊中儲存這些參數的區域很可能會在控制權從 call_pv 回傳時被其他東西覆寫。

使用 G_EVAL

以下是使用 G_EVAL 的範例。下列 Perl 子常式會計算其 2 個參數的差值。如果結果為負值,子常式會呼叫 die

sub Subtract
{
    my ($a, $b) = @_;

    die "death can be fatal\n" if $a < $b;

    $a - $b;
}

以及呼叫它的 C 程式碼

static void
call_Subtract(a, b)
int a;
int b;
{
    dSP;
    int count;
    SV *err_tmp;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 2);
    PUSHs(sv_2mortal(newSViv(a)));
    PUSHs(sv_2mortal(newSViv(b)));
    PUTBACK;

    count = call_pv("Subtract", G_EVAL|G_SCALAR);

    SPAGAIN;

    /* Check the eval first */
    err_tmp = ERRSV;
    if (SvTRUE(err_tmp))
    {
        printf ("Uh oh - %s\n", SvPV_nolen(err_tmp));
        POPs;
    }
    else
    {
      if (count != 1)
       croak("call_Subtract: wanted 1 value from 'Subtract', got %d\n",
             count);

        printf ("%d - %d = %d\n", a, b, POPi);
    }

    PUTBACK;
    FREETMPS;
    LEAVE;
}

如果 call_Subtract 這樣呼叫

call_Subtract(4, 5)

將會印出下列內容

Uh oh - death can be fatal

備註

  1. 我們希望能夠捕捉 die,因此我們使用了 G_EVAL 旗標。如果不指定這個旗標,程式會在子常式 Subtract 中的 die 陳述式立即終止。

  2. 程式碼

    err_tmp = ERRSV;
    if (SvTRUE(err_tmp))
    {
        printf ("Uh oh - %s\n", SvPV_nolen(err_tmp));
        POPs;
    }

    是這段 Perl 程式碼的直接等效項

    print "Uh oh - $@\n" if $@;

    PL_errgv 是 Perl 全域變數,類型為 GV *,它指向包含錯誤的符號表項目。因此,ERRSV 指向 $@ 的 C 等效項。我們使用一個區域暫存變數 err_tmp,因為 ERRSV 是呼叫函式的巨集,而 SvTRUE(ERRSV) 會重複呼叫那個函式多次。

  3. 請注意,當 SvTRUE(err_tmp) 為真時,會在區塊中使用 POPs 來彈出堆疊。這是必要的,因為每當使用 G_EVAL|G_SCALAR 呼叫 call_* 函數時,如果傳回錯誤,堆疊頂端會保留 undef 值。由於我們希望在偵測到此錯誤後程式能繼續執行,因此必須移除 undef 來整理堆疊。

使用 G_KEEPERR

考慮以下這個相當荒謬的範例,我們在解構函式中使用了上方 call_Subtract 範例的 XS 版本

    package Foo;
    sub new { bless {}, $_[0] }
    sub Subtract {
        my($a,$b) = @_;
        die "death can be fatal" if $a < $b;
        $a - $b;
    }
    sub DESTROY { call_Subtract(5, 4); }
    sub foo { die "foo dies"; }

    package main;
    {
	my $foo = Foo->new;
	eval { $foo->foo };
    }
    print "Saw: $@" if $@;             # should be, but isn't

此範例無法辨識在 eval {} 內部發生錯誤。原因如下:call_Subtract 程式碼在 perl 清除暫存資料並離開外部大括號區塊時執行,而且由於 call_Subtract 是使用 G_EVAL 旗標透過 call_pv 執行的,因此會立即重設 $@。這會導致最外層的 $@ 測試失敗,進而導致錯誤陷阱失敗。

附加 G_KEEPERR 旗標,讓 call_Subtract 中的 call_pv 呼叫讀取

count = call_pv("Subtract", G_EVAL|G_SCALAR|G_KEEPERR);

將保留錯誤並復原可靠的錯誤處理。

使用 call_sv

在所有先前的範例中,我都「硬寫」了要從 C 呼叫的 Perl 子常式的名稱。不過,大多數時候,從 Perl 腳本中指定 Perl 子常式名稱會比較方便,而且您會想要使用 call_sv

考慮以下 Perl 程式碼

sub fred
{
    print "Hello there\n";
}

CallSubPV("fred");

以下是定義 CallSubPV 的 XSUB 片段。

    void
    CallSubPV(name)
    	char *	name
    	CODE:
	PUSHMARK(SP);
	call_pv(name, G_DISCARD|G_NOARGS);

就目前而言,這很好。重點是,Perl 子常式只能指定為字串,但 Perl 允許參照子常式和匿名子常式。這正是 call_sv 有用的地方。

CallSubSV 的以下程式碼與 CallSubPV 相同,但 name 參數現在定義為 SV*,而且我們使用 call_sv 取代 call_pv

    void
    CallSubSV(name)
    	SV *	name
    	CODE:
	PUSHMARK(SP);
	call_sv(name, G_DISCARD|G_NOARGS);

由於我們使用 SV 來呼叫 fred,因此可以使用下列所有內容

CallSubSV("fred");
CallSubSV(\&fred);
$ref = \&fred;
CallSubSV($ref);
CallSubSV( sub { print "Hello there\n" } );

如您所見,call_sv 可讓您更靈活地指定 Perl 子常式。

您應注意,如果需要儲存與 Perl 子常式對應的 SV(上述範例中的 name),以便稍後在程式中使用,僅儲存 SV 指標的複本是不夠的。假設上述程式碼如下所示

    static SV * rememberSub;

    void
    SaveSub1(name)
    	SV *	name
    	CODE:
	rememberSub = name;

    void
    CallSavedSub1()
    	CODE:
	PUSHMARK(SP);
	call_sv(rememberSub, G_DISCARD|G_NOARGS);

這樣做錯誤的原因是,當您在 CallSavedSub1 中使用指標 rememberSub 時,它可能仍指向在 SaveSub1 中記錄的 Perl 子常式,也可能不再指向。這在下列情況中特別正確

SaveSub1(\&fred);
CallSavedSub1();

SaveSub1( sub { print "Hello there\n" } );
CallSavedSub1();

當上述每個 SaveSub1 陳述式執行完畢後,對應於參數的 SV* 將不再存在。預期 Perl 會傳送下列形式的錯誤訊息

Can't use an undefined value as a subroutine reference at ...

給每一個 CallSavedSub1 行。

同樣地,對於這個程式碼

$ref = \&fred;
SaveSub1($ref);
$ref = 47;
CallSavedSub1();

您可以預期收到下列其中一個訊息(實際收到的訊息取決於您使用的 Perl 版本)

Not a CODE reference at ...
Undefined subroutine &main::47 called ...

變數 $ref 可能在呼叫 SaveSub1 時指向子常式 fred,但在呼叫 CallSavedSub1 時,它現在儲存數字 47。因為我們在 SaveSub1 中僅儲存原始 SV 的指標,所以 $ref 的任何變更都會由指標 rememberSub 追蹤。這表示每當呼叫 CallSavedSub1 時,它將嘗試執行 SV* rememberSub 參照的程式碼。不過,在這個情況中,它現在指向整數 47,所以預期 Perl 會大聲抱怨。

這個程式碼說明了一個類似但更細微的問題

$ref = \&fred;
SaveSub1($ref);
$ref = \&joe;
CallSavedSub1();

這一次,每當呼叫 CallSavedSub1 時,它將執行 Perl 子常式 joe(假設它存在),而不是在呼叫 SaveSub1 時最初要求的 fred

要解決這些問題,必須取得 SV 的完整複本。下列程式碼顯示已修改為這樣做的 SaveSub2

    /* this isn't thread-safe */
    static SV * keepSub = (SV*)NULL;

    void
    SaveSub2(name)
        SV *	name
    	CODE:
     	/* Take a copy of the callback */
    	if (keepSub == (SV*)NULL)
    	    /* First time, so create a new SV */
	    keepSub = newSVsv(name);
    	else
    	    /* Been here before, so overwrite */
	    SvSetSV(keepSub, name);

    void
    CallSavedSub2()
    	CODE:
	PUSHMARK(SP);
	call_sv(keepSub, G_DISCARD|G_NOARGS);

為了避免每次呼叫 SaveSub2 時都建立新的 SV,此函式會先檢查它是否已在之前呼叫過。如果沒有,則會配置新 SV 的空間,並使用 newSVsv 將 Perl 子常式 name 的參照複製到變數 keepSub 中,這是一個操作。之後,每當呼叫 SaveSub2 時,現有的 SV,keepSub,就會使用 SvSetSV 覆寫為新值。

注意:使用靜態或全域變數來儲存 SV 並非執行緒安全。您可以使用 perlxs 中「在 XS 中安全儲存靜態資料」中記載的 MY_CXT 機制,其速度很快,或使用 get_sv() 將值儲存在 perl 全域變數中,其速度較慢。

使用 call_argv

以下是會列印傳遞給它的任何參數的 Perl 子常式。

sub PrintList
{
    my(@list) = @_;

    foreach (@list) { print "$_\n" }
}

以下是會呼叫 PrintListcall_argv 範例。

static char * words[] = {"alpha", "beta", "gamma", "delta", NULL};

static void
call_PrintList()
{
    call_argv("PrintList", G_DISCARD, words);
}

請注意,在這個範例中不需要呼叫 PUSHMARK。這是因為 call_argv 會為您執行此動作。

使用 call_method

考慮以下 Perl 程式碼

{
    package Mine;

    sub new
    {
        my($type) = shift;
        bless [@_]
    }

    sub Display
    {
        my ($self, $index) = @_;
        print "$index: $$self[$index]\n";
    }

    sub PrintID
    {
        my($class) = @_;
        print "This is Class $class version 1.0\n";
    }
}

它只實作了一個非常簡單的類別來管理陣列。除了建構函式 new 之外,它還宣告了方法,一個靜態方法和一個虛擬方法。靜態方法 PrintID 僅列印類別名稱和版本號碼。虛擬方法 Display 列印陣列的單一元素。以下是使用它的全 Perl 範例。

$a = Mine->new('red', 'green', 'blue');
$a->Display(1);
Mine->PrintID;

將列印

1: green
This is Class Mine version 1.0

從 C 呼叫 Perl 方法相當簡單。需要以下事項

以下是一個簡單的 XSUB,說明從 C 呼叫 PrintIDDisplay 方法的機制。

void
call_Method(ref, method, index)
    SV *	ref
    char *	method
    int		index
    CODE:
    PUSHMARK(SP);
    EXTEND(SP, 2);
    PUSHs(ref);
    PUSHs(sv_2mortal(newSViv(index)));
    PUTBACK;

    call_method(method, G_DISCARD);

void
call_PrintID(class, method)
    char *	class
    char *	method
    CODE:
    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newSVpv(class, 0)));
    PUTBACK;

    call_method(method, G_DISCARD);

因此,可以像這樣呼叫 PrintIDDisplay 方法

$a = Mine->new('red', 'green', 'blue');
call_Method($a, 'Display', 1);
call_PrintID('Mine', 'PrintID');

唯一要注意的是,在靜態和虛擬方法中,方法名稱並未透過堆疊傳遞,而是用作 call_method 的第一個參數。

使用 GIMME_V

以下是一個微不足道的 XSUB,它會列印它目前執行的內容。

void
PrintContext()
    CODE:
    U8 gimme = GIMME_V;
    if (gimme == G_VOID)
        printf ("Context is Void\n");
    else if (gimme == G_SCALAR)
        printf ("Context is Scalar\n");
    else
        printf ("Context is Array\n");

以下是測試它的 Perl 程式碼。

PrintContext;
$a = PrintContext;
@a = PrintContext;

其輸出將為

Context is Void
Context is Scalar
Context is Array

使用 Perl 捨棄暫存變數

在迄今為止提供的範例中,在回呼中建立的任何暫存變數(即傳遞給 call_* 函式的堆疊參數或透過堆疊傳回的值)都已透過以下方法之一釋放

還有另一種方法可以使用,即在回呼終止後 Perl 重新取得控制權時,讓 Perl 自動為您執行此動作。這只需不使用

ENTER;
SAVETMPS;
...
FREETMPS;
LEAVE;

序列在回呼中(當然,不指定 G_DISCARD 旗標)。

如果您要使用此方法,您必須注意可能發生的記憶體外洩,這會在非常特定的情況下發生。要說明這些情況,您需要了解 Perl 與回呼常式之間的控制流程。

文件開頭給的範例(錯誤處理常式和事件驅動程式)是您可能會在回呼中遇到的兩種主要流程控制類型的典型範例。它們之間有非常重要的區別,所以請注意。

在第一個範例,錯誤處理常式中,控制流程可能是這樣。您已建立外部程式庫的介面。控制可以像這樣到達外部程式庫

perl --> XSUB --> external library

當控制在程式庫中時,會發生錯誤狀況。您先前已設定 Perl 回呼來處理此狀況,所以它會執行。一旦回呼完成,控制會再次回到 Perl。以下是控制流程在那個狀況中會是什麼樣子

perl --> XSUB --> external library
                  ...
                  error occurs
                  ...
                  external library --> call_* --> perl
                                                      |
perl <-- XSUB <-- external library <-- call_* <----+

使用 call_* 處理錯誤後,控制會立即或多或少地回到 Perl。

在圖表中,您往右邊走,範圍就會越深入。只有當控制回到圖表最左邊的 Perl 時,您才會回到封閉範圍,而且您留下的任何暫存資料都會被釋放。

在第二個範例,事件驅動程式中,控制流程會更像這樣

perl --> XSUB --> event handler
                  ...
                  event handler --> call_* --> perl
                                                   |
                  event handler <-- call_* <----+
                  ...
                  event handler --> call_* --> perl
                                                   |
                  event handler <-- call_* <----+
                  ...
                  event handler --> call_* --> perl
                                                   |
                  event handler <-- call_* <----+

在這種情況下,控制流程可能只包含重複的序列

event handler --> call_* --> perl

幾乎在整個程式執行期間。這表示控制可能 永遠 都不會回到 Perl 中最左邊的周圍範圍。

所以大問題是什麼?好吧,如果您預期 Perl 會為您整理那些暫存資料,您可能得等很久。Perl 要處置您的暫存資料,控制必須在某個階段回到封閉範圍。在事件驅動的場景中,這可能永遠不會發生。這表示,隨著時間推移,您的程式會建立越來越多暫存資料,而且這些暫存資料都不會被釋放。由於這些暫存資料每一個都會消耗一些記憶體,您的程式最終會耗盡系統中所有可用的記憶體——爆炸!

所以重點是——如果您確定控制會在您的回呼結束後相當快地回到封閉 Perl 範圍,那麼就沒有絕對必要明確地處置您可能已建立的任何暫存資料。請注意,如果您對於要怎麼做一點都不確定,整理一下並不會造成任何傷害。

儲存回呼內容資訊的策略

在設計回呼介面時,潛在最棘手的問題之一可能是找出如何儲存 C 回呼函式與 Perl 等效項之間的對應。

要了解為什麼這會是一個真正的問題,首先考慮在全 C 環境中如何設定回呼。通常,C API 會提供一個函式來註冊回呼。這會將函式指標當作其中一個參數。以下是對假設函式 register_fatal 的呼叫,它會註冊 C 函式,以便在發生致命錯誤時呼叫。

register_fatal(cb1);

單一參數 cb1 是函式指標,因此您必須在程式碼中定義 cb1,例如像這樣

static void
cb1()
{
    printf ("Fatal Error\n");
    exit(1);
}

現在將其變更為呼叫 Perl 子常式

static SV * callback = (SV*)NULL;

static void
cb1()
{
    dSP;

    PUSHMARK(SP);

    /* Call the Perl sub to process the callback */
    call_sv(callback, G_DISCARD);
}


void
register_fatal(fn)
    SV *	fn
    CODE:
    /* Remember the Perl sub */
    if (callback == (SV*)NULL)
        callback = newSVsv(fn);
    else
        SvSetSV(callback, fn);

    /* register the callback with the external library */
    register_fatal(cb1);

其中 register_fatal 的 Perl 等效項及其註冊的回呼 pcb1 可能如下所示

# Register the sub pcb1
register_fatal(\&pcb1);

sub pcb1
{
    die "I'm dying...\n";
}

C 回呼與 Perl 等效項之間的對應儲存在全域變數 callback 中。

如果您只需要隨時註冊一個回呼,這就足夠了。一個範例可能是像上面所述的錯誤處理常式。不過,請記住,重複呼叫 register_fatal 會將先前註冊的回呼函式替換為新的回呼函式。

例如,假設您想要介接允許非同步檔案 I/O 的函式庫。在這種情況下,您可以在每次讀取作業完成時註冊回呼。為了發揮任何作用,我們希望能夠為每個開啟的檔案呼叫個別的 Perl 子常式。就目前而言,上述的錯誤處理常式範例並不合適,因為它只允許在任何時候定義一個回呼。我們需要的是一種方法來儲存開啟的檔案與我們希望為該檔案呼叫的 Perl 子常式之間的對應。

假設 I/O 函式庫有一個函式 asynch_read,它會將 C 函式 ProcessRead 與檔案處理序 fh 關聯起來,這假設它也提供了一些常式來開啟檔案,並取得檔案處理序。

asynch_read(fh, ProcessRead)

這可能會預期此表單的 C ProcessRead 函式

void
ProcessRead(fh, buffer)
int	fh;
char *	buffer;
{
     ...
}

要為此函式庫提供 Perl 介面,我們需要能夠在 fh 參數與我們希望呼叫的 Perl 子常式之間進行對應。雜湊是一種用於儲存此對應的便利機制。以下程式碼顯示可能的實作

static HV * Mapping = (HV*)NULL;

void
asynch_read(fh, callback)
    int	fh
    SV *	callback
    CODE:
    /* If the hash doesn't already exist, create it */
    if (Mapping == (HV*)NULL)
        Mapping = newHV();

    /* Save the fh -> callback mapping */
    hv_store(Mapping, (char*)&fh, sizeof(fh), newSVsv(callback), 0);

    /* Register with the C Library */
    asynch_read(fh, asynch_read_if);

asynch_read_if 可能如下所示

static void
asynch_read_if(fh, buffer)
int	fh;
char *	buffer;
{
    dSP;
    SV ** sv;

    /* Get the callback associated with fh */
    sv =  hv_fetch(Mapping, (char*)&fh , sizeof(fh), FALSE);
    if (sv == (SV**)NULL)
        croak("Internal error...\n");

    PUSHMARK(SP);
    EXTEND(SP, 2);
    PUSHs(sv_2mortal(newSViv(fh)));
    PUSHs(sv_2mortal(newSVpv(buffer, 0)));
    PUTBACK;

    /* Call the Perl sub */
    call_sv(*sv, G_DISCARD);
}

為了完整性,以下是 asynch_close。它顯示如何從雜湊 Mapping 中移除項目。

void
asynch_close(fh)
    int	fh
    CODE:
    /* Remove the entry from the hash */
    (void) hv_delete(Mapping, (char*)&fh, sizeof(fh), G_DISCARD);

    /* Now call the real asynch_close */
    asynch_close(fh);

因此,Perl 介面會如下所示

sub callback1
{
    my($handle, $buffer) = @_;
}

# Register the Perl callback
asynch_read($fh, \&callback1);

asynch_close($fh);

這次,C 回呼與 Perl 之間的對應儲存在全域雜湊 Mapping 中。使用雜湊具有顯著的優點,因為它允許註冊無限數量的回呼。

如果 C 回呼提供的介面不包含允許檔案處理序對應到 Perl 子常式的參數,該怎麼辦?假設在非同步 I/O 套件中,回呼函式只傳遞 buffer 參數,如下所示

void
ProcessRead(buffer)
char *	buffer;
{
    ...
}

沒有檔案處理序,就沒有直接的方法可以從 C 回呼對應到 Perl 子常式。

在這種情況下,解決此問題的一種可能方法是預先定義一系列 C 函數作為 Perl 的介面,如下所示

#define MAX_CB		3
#define NULL_HANDLE	-1
typedef void (*FnMap)();

struct MapStruct {
    FnMap    Function;
    SV *     PerlSub;
    int      Handle;
  };

static void  fn1();
static void  fn2();
static void  fn3();

static struct MapStruct Map [MAX_CB] =
    {
        { fn1, NULL, NULL_HANDLE },
        { fn2, NULL, NULL_HANDLE },
        { fn3, NULL, NULL_HANDLE }
    };

static void
Pcb(index, buffer)
int index;
char * buffer;
{
    dSP;

    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newSVpv(buffer, 0)));
    PUTBACK;

    /* Call the Perl sub */
    call_sv(Map[index].PerlSub, G_DISCARD);
}

static void
fn1(buffer)
char * buffer;
{
    Pcb(0, buffer);
}

static void
fn2(buffer)
char * buffer;
{
    Pcb(1, buffer);
}

static void
fn3(buffer)
char * buffer;
{
    Pcb(2, buffer);
}

void
array_asynch_read(fh, callback)
    int		fh
    SV *	callback
    CODE:
    int index;
    int null_index = MAX_CB;

    /* Find the same handle or an empty entry */
    for (index = 0; index < MAX_CB; ++index)
    {
        if (Map[index].Handle == fh)
            break;

        if (Map[index].Handle == NULL_HANDLE)
            null_index = index;
    }

    if (index == MAX_CB && null_index == MAX_CB)
        croak ("Too many callback functions registered\n");

    if (index == MAX_CB)
        index = null_index;

    /* Save the file handle */
    Map[index].Handle = fh;

    /* Remember the Perl sub */
    if (Map[index].PerlSub == (SV*)NULL)
        Map[index].PerlSub = newSVsv(callback);
    else
        SvSetSV(Map[index].PerlSub, callback);

    asynch_read(fh, Map[index].Function);

void
array_asynch_close(fh)
    int	fh
    CODE:
    int index;

    /* Find the file handle */
    for (index = 0; index < MAX_CB; ++ index)
        if (Map[index].Handle == fh)
            break;

    if (index == MAX_CB)
        croak ("could not close fh %d\n", fh);

    Map[index].Handle = NULL_HANDLE;
    SvREFCNT_dec(Map[index].PerlSub);
    Map[index].PerlSub = (SV*)NULL;

    asynch_close(fh);

在這種情況下,函數 fn1fn2fn3 用於記住要呼叫的 Perl 子程式。每個函數都包含一個獨立的硬連線索引,在函數 Pcb 中使用此索引來存取 Map 陣列並實際呼叫 Perl 子程式。

使用此技術有一些明顯的缺點。

首先,程式碼比前一個範例複雜得多。

其次,同時存在的回呼數量有一個硬連線限制(在本例中為 3)。增加限制的唯一方法是修改程式碼以新增更多函數,然後重新編譯。儘管如此,只要仔細選擇函數數量,它仍然是一個可行的解決方案,在某些情況下是唯一可用的解決方案。

總之,以下列出一些可能的方法供您考慮,用於儲存 C 和 Perl 回呼之間的對應

1. 忽略問題 - 僅允許 1 個回呼

對於許多情況,例如與錯誤處理常式建立介面,這可能是一個完全充分的解決方案。

2. 建立一個回呼序列 - 硬連線限制

如果無法從 C 回呼傳回的參數中得知內容為何,則可能需要建立一個 C 回呼介面函數序列,並將指標儲存在陣列中。

3. 使用參數對應到 Perl 回呼

雜湊是儲存 C 和 Perl 之間對應的理想機制。

替代堆疊處理

雖然我只使用 POP* 巨集來存取從 Perl 子程式傳回的值,但也可以繞過這些巨集並使用 ST 巨集來讀取堆疊(請參閱 perlxs 以取得 ST 巨集的完整說明)。

大多數時候,POP* 巨集應該是足夠的;它們的主要問題是它們強迫您按順序處理傳回的值。在某些情況下,這可能不是處理值的最佳方式。我們想要的是能夠以隨機順序存取堆疊。在編寫 XSUB 時使用的 ST 巨集非常適合此目的。

以下程式碼是 "傳回值清單" 區段中給出的範例,重新編碼為使用 ST 而不是 POP*

static void
call_AddSubtract2(a, b)
int a;
int b;
{
    dSP;
    I32 ax;
    int count;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 2);
    PUSHs(sv_2mortal(newSViv(a)));
    PUSHs(sv_2mortal(newSViv(b)));
    PUTBACK;

    count = call_pv("AddSubtract", G_LIST);

    SPAGAIN;
    SP -= count;
    ax = (SP - PL_stack_base) + 1;

    if (count != 2)
        croak("Big trouble\n");

    printf ("%d + %d = %d\n", a, b, SvIV(ST(0)));
    printf ("%d - %d = %d\n", a, b, SvIV(ST(1)));

    PUTBACK;
    FREETMPS;
    LEAVE;
}

備註

  1. 請注意,有必要定義變數 ax。這是因為 ST 巨集預期它存在。如果我們在 XSUB 中,則不需要定義 ax,因為它已經為我們定義好了。

  2. 程式碼

    SPAGAIN;
    SP -= count;
    ax = (SP - PL_stack_base) + 1;

    設定堆疊,以便我們可以使用 ST 巨集。

  3. 與此範例的原始編碼不同,傳回的值並非以相反的順序存取。因此,ST(0) 指的是 Perl 子常式傳回的第一個值,而 ST(count-1) 指的是最後一個值。

在 C 中建立並呼叫匿名子常式

正如我們已經展示的,call_sv 可用於呼叫匿名子常式。不過,我們的範例展示了一個 Perl 腳本呼叫 XSUB 來執行此操作。讓我們看看如何在我們的 C 程式碼中執行此操作

...

SV *cvrv
   = eval_pv("sub {
               print 'You will not find me cluttering any namespace!'
              }", TRUE);

...

call_sv(cvrv, G_VOID|G_NOARGS);

eval_pv 用於編譯匿名子常式,它也會是傳回值(在 perlapi 中的「eval_pv」 中進一步瞭解 eval_pv)。一旦取得此程式碼參照,即可將它與我們之前展示的所有範例混合在一起。

輕量級回呼

有時您需要重複呼叫同一個子常式。這通常會發生在作用於值清單的函式中,例如 Perl 內建的 sort()。您可以傳遞一個比較函式給 sort(),然後它會針對需要比較的所有值對呼叫此函式。來自 List::Util 的 first() 和 reduce() 函式遵循類似的模式。

在這種情況下,可以使用輕量級回呼 API 來加速常式(通常可以大幅加速)。這個想法是呼叫內容只需要建立並銷毀一次,而子常式可以在其間任意呼叫多次。

通常使用全域變數(通常為單一參數的 $_,或兩個參數的 $a 和 $b)傳遞參數,而不是透過 @_。(如果您知道自己在做什麼,可以使用 @_ 機制,儘管目前還沒有支援的 API。它本質上也比較慢。)

巨集呼叫的模式如下

    dMULTICALL;			/* Declare local variables */
    U8 gimme = G_SCALAR;	/* context of the call: G_SCALAR,
				 * G_LIST, or G_VOID */

    PUSH_MULTICALL(cv);		/* Set up the context for calling cv,
				   and set local vars appropriately */

    /* loop */ {
        /* set the value(s) af your parameter variables */
        MULTICALL;		/* Make the actual call */
    } /* end of loop */

    POP_MULTICALL;		/* Tear down the calling context */

有關具體範例,請參閱 List::Util 1.18 的 first() 和 reduce() 函式的實作。您還可以在其中找到一個標頭檔案,它模擬了較舊版本的 perl 上的多重呼叫 API。

另請參閱

perlxsperlgutsperlembed

作者

Paul Marquess

特別感謝以下協助建立此文件的人員。

Jeff Okamoto、Tim Bunce、Nick Gianniotis、Steve Kelem、Gurusamy Sarathy 和 Larry Wall。

日期

最後更新於 perl 5.23.1。