ユーザ定義の関数はC(もしくはC++のようなCと互換性のある言語)で作成することができます。 そのような関数は動的ロード可能オブジェクト(共有ライブラリとも呼ばれます)としてコンパイルされ、必要に応じてサーバにロードされます。 動的ロード機能が、「C言語」関数を「内部」関数と区別するものです。 コーディング方法は基本的に両方とも同じです。 (したがって、標準内部関数ライブラリはユーザ定義のC関数のコーディング例の豊富な情報源となります。)
現在、1つの呼び出し規約だけがC関数で使用されています(「version 1」)。
その呼び出し規約をサポートしていることは、以下に示すように、その関数用に呼び出しマクロPG_FUNCTION_INFO_V1()
を書くことで示されます。
特定のロード可能オブジェクト内のユーザ定義の関数がセッションで最初に呼び出されると、動的ローダは、その関数を呼び出すことができるように、オブジェクトファイルをメモリ内に読み込みます。
そのため、ユーザ定義のC関数用のCREATE FUNCTION
はその関数について、ロード可能オブジェクトファイルの名前とオブジェクトファイル中の呼び出される特定の関数のC名称(リンクシンボル)という2つの情報を指定しなければなりません。
C名称が明示的に指定されなかった場合、SQLにおける関数名と同じものと仮定されます。
CREATE FUNCTION
コマンドで与えられた名前に基づいて、共有オブジェクトファイルの場所を見つける際に以下のアルゴリズムが使用されます。
名前が絶対パスの場合、指定されたファイルが読み込まれます。
名前が$libdir
という文字列から始まる場合、その部分はPostgreSQLパッケージのライブラリディレクトリで置き換えられます。
このディレクトリはビルド時に決定されます。
名前にディレクトリ部分がない場合、そのファイルはdynamic_library_path設定変数で指定されたパス内から検索されます。
上記以外の場合(ファイルがパス内に存在しない場合や相対ディレクトリ部分を持つ場合)、動的ローダは指定された名前をそのまま使用し、ほとんどの場合は失敗します。 (これは現在の作業ディレクトリに依存するため信頼できません。)
ここまでの流れがうまくいかなかった場合、プラットフォーム独自の共有ライブラリファイル拡張子(多くの場合.so
)が指定された名前に追加され、再度この流れを試みます。
同様に失敗した場合は、読み込みは失敗します。
共有ライブラリを$libdir
から相対的に、もしくは動的ライブラリパスの通った所に配置することを推奨します。
異なる場所に新しいインストレーションを配置する場合にバージョンアップを簡単にします。
$libdir
が示す実際のディレクトリはpg_config --pkglibdir
コマンドを使用することでわかります。
PostgreSQLサーバの実効ユーザIDはロード予定のファイルのパスまで到達できなければなりません。 よくある失敗として、postgresユーザに対して読み込み、実行、または両方の権限がそのファイルとその上位ディレクトリに与えられていないことがあります。
どの場合でも、CREATE FUNCTION
コマンドに与えたファイル名はそのままシステムカタログに保存されます。
ですので、もしそのファイルを再度読み込む必要がある場合、同じ処理が適用されます。
PostgreSQLはC関数を自動的にコンパイルしません。
CREATE FUNCTION
コマンドで参照する前に、そのオブジェクトファイルはコンパイルされていなければなりません。
さらなる情報については37.10.5を参照してください。
確実に、動的にロードされるモジュールが互換性がないサーバにロードされないように、PostgreSQLは、そのファイルに適切な内容を持つ「マジックブロック」が含まれているかどうか検査します。
これによりサーバは、メジャーバージョンが異なるPostgreSQL用にコンパイルされたモジュールなど、明確に互換性がないことを検知することができます。
マジックブロックを含めるためには、以下をモジュールのソースファイルに一度(一度だけ)、fmgr.h
ヘッダファイルをincludeさせた後で、記述してください。
PG_MODULE_MAGIC;
最初に使用された後も、動的にロードされたオブジェクトファイルはメモリ内に保持されます。 同一セッションにおいてそのファイル内の関数をその後に呼び出した場合、シンボルテーブルの検索に要する小さなオーバーヘッドしかかかりません。 例えば再コンパイルした後など、そのオブジェクトファイルを強制的に再度読み込ませる必要がある場合は、新しいセッションを開始してください。
省略することもできますが、動的にロードされるファイルに初期化処理関数と最終処理関数を含めることができます。
_PG_init
という関数がファイルに存在すると、この関数はファイルがロードされた直後に呼び出されます。
この関数は引数を取らずvoid型を返さなければなりません。
_PG_fini
という関数がファイルに存在すると、この関数はファイルがアンロードされる直前に呼び出されます。
この関数も同様に引数を取らずvoid型を返さなければなりません。
_PG_fini
がファイルのアンロード時にのみ呼び出されるものであり、処理の終了時に呼び出されるものではないことに注意してください。
(現在、アンロードは無効となっていますので、決して発生しません。将来変更される可能性があります。)
C言語関数の作成方法を理解するためには、PostgreSQLが基本データ型を内部でどのように表現し、どのようにそれらを関数とやり取りしているかを理解する必要があります。 内部的にPostgreSQLは基本型を「メモリの小さな塊」とみなします。 ある型を定義するユーザ定義関数は、言い換えると、PostgreSQLがそれを操作できる方法を定義します。 つまり、PostgreSQLはデータの格納、ディスクからの取り出しのみを行い、データの入力や処理、出力にはユーザ定義関数を使用します。
基本型は下記の3つのいずれかの内部書式を使用しています。
固定長の値渡し
固定長の参照渡し
可変長の参照渡し
値渡しは、1、2、4バイト長の型のみで使用することができます(使用するマシンのsizeof(Datum)
が8の場合は8バイトも使用できます)。
データ型を定義する際、その型がすべてのアーキテクチャにおいて同一の大きさ(バイト数)となるように定義するように注意してください。
例えば、long
型はマシンによっては4バイトであったり、8バイトであったりして危険ですが、int
型はほとんどのUnixマシンでは4バイトです。
Unixマシンにおけるint4
の理論的な実装は以下のようになります。
/* 4 バイト整数、値渡し */ typedef int int4;
(実際のPostgreSQLのCコードではこの型をint32
と呼びます。
int
がXX
XX
ビットであることはCにおける規約だからです。
したがってint8
というCの型のサイズは1バイトであることに注意してください。
int8
というSQLの型はCではint64
と呼ばれます。
表 37.2も参照してください。)
一方、任意の大きさの固定長の型は参照として引き渡すことができます。 例として以下にPostgreSQLの型の実装サンプルを示します。
/* 16 バイト構造体、参照渡し */ typedef struct { double x, y; } Point;
それらの型のポインタのみがPostgreSQL関数の入出力時に使用できます。
それらの型の値を返すためには、palloc()
を使用して正しい大きさのメモリ領域を割り当て、そのメモリ領域に値を入力し、それのポインタを返します。
(また、入力引数の1つと同じ型かつ同じ値を返したいのであれば、palloc
を行う手間を省くことができます。
この場合は入力値へのポインタを単に返してください。)
最後に、すべての可変長型は参照として引き渡す必要があります。
また、すべての可変長型は正確に4バイトの不透明なlengthフィールドから始まる必要があります。
このフィールドはSET_VARSIZE
で設定されます。決して直接このフィールドを設定してはいけません。
その型に格納されるすべてのデータはlengthフィールドのすぐ後のメモリ領域に置かれる必要があります。
lengthフィールドにはその構造体の総長が格納されます。つまり、lengthフィールドそのものもその大きさに含まれます。
この他の重要な点は、データ型の値の中で初期化されていないビットを残さないことです。 例えば、構造体内に存在する可能性がある整列用のパディングバイトを注意してすべてゼロクリアしてください。 こうしないと、独自データ型の論理的に等価な定数がプランナにより一致しないものと判断され、(不正確ではありませんが)非効率的な計画をもたらすかもしれません。
参照渡しの入力値の内容を決して変更しないでください。 指定したポインタがディスクバッファを直接指し示している可能性がよくありますので、変更すると、ディスク上のデータを破壊してしまうかもしれません。 この規則の唯一の例外について37.12で説明します。
例えば、text
型を定義するには、下記のように行えます。
typedef struct { int32 length; char data[FLEXIBLE_ARRAY_MEMBER]; } text;
[FLEXIBLE_ARRAY_MEMBER]
表記は、データ部分の実際の長さはこの宣言では指定されないことを意味します。
可変長型を操作する時、正確な大きさのメモリを割り当て、lengthフィールドを正確に設定することに注意する必要があります。
例えば、40バイトをtext
構造体に保持させたい場合、下記のようなコードを使用します。
#include "postgres.h" ... char buffer[40]; /* 私たちの元のデータ */ ... text *destination = (text *) palloc(VARHDRSZ + 40); SET_VARSIZE(destination, VARHDRSZ + 40); memcpy(destination->data, buffer, 40); ...
VARHDRSZ
はsizeof(int32)
と同一ですが、可変長型のオーバーヘッド分の大きさを参照する時には、VARHDRSZ
マクロを使用する方が好ましい形式とみなされています。
また長さフィールドを単なる代入ではなくSET_VARSIZE
マクロを使用して設定しなければなりません。
表 37.2は、PostgreSQLの組み込み型を使用するC言語関数を作成する時の、Cの型とSQL型との対応を規定したものです。
「定義場所」列では、型定義を取り出すためにインクルードしなければならないヘッダファイルを示しています。
(実際の定義は一覧中のファイルからインクルードされた、別のファイルであるかもしれません。
ユーザは定義されたインタフェースを厳守することを推奨されています。)
postgres.h
には必ず必要になる多くのものが宣言されていますので、ソースファイルの中で必ず初めにこのファイルをインクルードしなければならないことに注意してください。
表37.2 組み込みSQL型に相当するCの型
SQL型 | C 言語型 | 定義場所 |
---|---|---|
boolean | bool | postgres.h (コンパイラで組み込み済みの可能性があります) |
box | BOX* | utils/geo_decls.h |
bytea | bytea* | postgres.h |
"char" | char | (コンパイラで組み込み済み) |
character | BpChar* | postgres.h |
cid | CommandId | postgres.h |
date | DateADT | utils/date.h |
smallint (int2 ) | int16 | postgres.h |
int2vector | int2vector* | postgres.h |
integer (int4 ) | int32 | postgres.h |
real (float4 ) | float4* | postgres.h |
double precision (float8 ) | float8* | postgres.h |
interval | Interval* | datatype/timestamp.h |
lseg | LSEG* | utils/geo_decls.h |
name | Name | postgres.h |
oid | Oid | postgres.h |
oidvector | oidvector* | postgres.h |
path | PATH* | utils/geo_decls.h |
point | POINT* | utils/geo_decls.h |
regproc | regproc | postgres.h |
text | text* | postgres.h |
tid | ItemPointer | storage/itemptr.h |
time | TimeADT | utils/date.h |
time with time zone | TimeTzADT | utils/date.h |
timestamp | Timestamp | datatype/timestamp.h |
varchar | VarChar* | postgres.h |
xid | TransactionId | postgres.h |
ここまでで基本型に関してあり得る構造体のすべてを記述しましたので、実際の関数の例をいくつか示すことができます。
Version-1呼び出し規約では、引数と結果の引き渡しの複雑さをなくすためにマクロを使用しています。 Version-1関数のC言語宣言は必ず下記のように行います。
Datum funcname(PG_FUNCTION_ARGS)
さらに、マクロ呼び出し
PG_FUNCTION_INFO_V1(funcname);
が同じソースファイルに書かれている必要があります。
(一般には、関数の直前に書かれます。)
PostgreSQLではすべての内部関数はVersion-1であると認識するので、このマクロの呼び出しはinternal
言語関数では必要ありません。
しかし、動的にロードされる関数では必要です。
Version-1関数では、それぞれの実引数は、引数のデータ型に合ったPG_GETARG_
マクロを使用して取り出されます。
(厳格でない関数では、xxx
()PG_ARGISNULL()
を使って引数がNULLかどうか事前に確認することが必要です。下記参照。)
結果は戻り値の型に合ったPG_RETURN_
マクロを使用して返されます。
xxx
()PG_GETARG_
は、その引数として、取り出す関数引数の番号(ゼロから始まります)を取ります。
xxx
()PG_RETURN_
は、その引数として、実際に返す値を取ります。
xxx
()
Version-1呼出し規約を使った例をいくつか以下に示します。
#include "postgres.h" #include <string.h> #include "fmgr.h" #include "utils/geo_decls.h" PG_MODULE_MAGIC; /* 値渡し */ PG_FUNCTION_INFO_V1(add_one); Datum add_one(PG_FUNCTION_ARGS) { int32 arg = PG_GETARG_INT32(0); PG_RETURN_INT32(arg + 1); } /* 固定長の参照渡し */ PG_FUNCTION_INFO_V1(add_one_float8); Datum add_one_float8(PG_FUNCTION_ARGS) { /* FLOAT8用のマクロは参照渡しという性質を隠します */ float8 arg = PG_GETARG_FLOAT8(0); PG_RETURN_FLOAT8(arg + 1.0); } PG_FUNCTION_INFO_V1(makepoint); Datum makepoint(PG_FUNCTION_ARGS) { /* ここのPoint型の参照渡しという性質は隠されていません */ Point *pointx = PG_GETARG_POINT_P(0); Point *pointy = PG_GETARG_POINT_P(1); Point *new_point = (Point *) palloc(sizeof(Point)); new_point->x = pointx->x; new_point->y = pointy->y; PG_RETURN_POINT_P(new_point); } /* 可変長の参照渡し */ PG_FUNCTION_INFO_V1(copytext); Datum copytext(PG_FUNCTION_ARGS) { text *t = PG_GETARG_TEXT_PP(0); /* * VARSIZEは、そのヘッダのVARHDRSZまたはVARHDRSZ_SHORTを引いた * 構造体の総長をバイト数で表したものです。 * 完全な長さのヘッダと合わせたコピーを作成します。 */ text *new_t = (text *) palloc(VARSIZE_ANY_EXHDR(t) + VARHDRSZ); SET_VARSIZE(new_t, VARSIZE_ANY_EXHDR(t) + VARHDRSZ); /* * VARDATAは新しい構造体のデータ領域へのポインタです。 * コピー元はshortデータかもしれませんので、VARDATA_ANYでデータを取り出します。 */ memcpy((void *) VARDATA(new_t), /* コピー先 */ (void *) VARDATA_ANY(t), /* コピー元 */ VARSIZE_ANY_EXHDR(t)); /* バイト数 */ PG_RETURN_TEXT_P(new_t); } PG_FUNCTION_INFO_V1(concat_text); Datum concat_text(PG_FUNCTION_ARGS) { text *arg1 = PG_GETARG_TEXT_PP(0); text *arg2 = PG_GETARG_TEXT_PP(1); int32 arg1_size = VARSIZE_ANY_EXHDR(arg1); int32 arg2_size = VARSIZE_ANY_EXHDR(arg2); int32 new_text_size = arg1_size + arg2_size + VARHDRSZ; text *new_text = (text *) palloc(new_text_size); SET_VARSIZE(new_text, new_text_size); memcpy(VARDATA(new_text), VARDATA_ANY(arg1), arg1_size); memcpy(VARDATA(new_text) + arg1_size, VARDATA_ANY(arg2), arg2_size); PG_RETURN_TEXT_P(new_text); }
上のコードがファイルfuncs.c
に用意されていて、共有オブジェクトにコンパイルされているとしたら、以下のようにPostgreSQLにコマンドで関数を定義できます。
CREATE FUNCTION add_one(integer) RETURNS integer AS 'DIRECTORY
/funcs', 'add_one' LANGUAGE C STRICT; -- SQL関数名"add_one"のオーバーロードに注意 CREATE FUNCTION add_one(double precision) RETURNS double precision AS 'DIRECTORY
/funcs', 'add_one_float8' LANGUAGE C STRICT; CREATE FUNCTION makepoint(point, point) RETURNS point AS 'DIRECTORY
/funcs', 'makepoint' LANGUAGE C STRICT; CREATE FUNCTION copytext(text) RETURNS text AS 'DIRECTORY
/funcs', 'copytext' LANGUAGE C STRICT; CREATE FUNCTION concat_text(text, text) RETURNS text AS 'DIRECTORY
/funcs', 'concat_text' LANGUAGE C STRICT;
ここでは、DIRECTORY
は共有ライブラリファイルのディレクトリ(例えばPostgreSQLのチュートリアルのディレクトリ、そこにはこの節で使われている例のコードがあります)を表しています。
(DIRECTORY
を検索パスに追加した後にAS
句で'funcs'
だけを使うのがより良いやり方でしょう。
どの場合でも、共有ライブラリを表すシステムに特有の拡張子、普通は.so
を省略できます。)
関数を「strict」と指定したことに注意してください。これは入力値のいずれかがNULLだった場合、システムが自動的に結果をNULLと決めてしまうことを意味します。
こうすることで、関数のコード内でNULLの入力を確認しなければならないことを避けています。
これがなければ、PG_ARGISNULL()
を使ってNULL値を明示的に確認しなければなりません。
PG_ARGISNULL(
マクロにより関数は各入力がNULLであるかどうかの検査を行うことができます。
(もちろんこれは、「厳密」と宣言されていない関数でのみ必要です。)
n
)PG_GETARG_
マクロと同様、入力引数の番号はゼロから始まります。
引数がNULLでないことを確認するまでは、xxx
()PG_GETARG_
の実行は控えなければなりません。
結果としてNULLを返す場合は、xxx
()PG_RETURN_NULL()
を実行します。
これは、厳密な関数と厳密でない関数の両方で使用可能です。
一見、Version-1のコーディング規約は、普通のC
の呼出し規約と比較すると、無意味なあいまいなものの様に見えるかもしれません。
しかし、NULL
になりうる引数や戻り値、「TOASTされた」(圧縮または行外)値を扱うことができます。
Version 1のインタフェースでは、その他のオプションとしてPG_GETARG_
マクロの変形を2つ提供しています。
1つ目のxxx
()PG_GETARG_
によって、安全に書き込むことができる指定引数のコピーが確実に返されます。
(通常のマクロは、物理的にテーブルに格納されている値へのポインタを返すことがあるので、書き込んではなりません。
xxx
_COPY()PG_GETARG_
マクロの結果は書き込み可能であることが保証されています。)
2つ目の変形は、引数を3つ取るxxx
_COPY()PG_GETARG_
マクロからなります。
1つ目は関数の引数の番号(上記の通り)です。
2つ目と3つ目は、オフセットと返されるセグメントの長さです。
オフセットはゼロから始まり、負の長さは残りの値を返すことを要求します。
これらのマクロを使用すると、ストレージ種類が「external」(外部)である大きな値の一部へアクセスする際に非常に効果的です。
(列のストレージ種類はxxx
_SLICE()ALTER TABLE
を使用して指定できます。
tablename
ALTER COLUMN colname
SET STORAGE storagetype
storagetype
は、plain
、external
、extended
、またはmain
のいずれかです。)
最後に、Version-1関数呼び出し規約では、結果集合(37.10.8)を返すこと、およびトリガ関数(第38章)と手続型言語の呼び出しハンドラ(第55章)を実装することができます。
詳細についてはソース配布物内のsrc/backend/utils/fmgr/README
を参照してください。
より先進的な話題に入る前に、PostgreSQL C言語関数のコーディングについての規則をいくつか説明します。 C言語以外の言語で記述した関数をPostgreSQLに組み込みむことは可能であるかもしれませんが、例えばC++、FORTRANやPascalといった言語はC言語と同じ呼び出し規約に従いませんので、多くの場合、(可能であったとしても)困難です。 それはつまり、他の言語では同じ方法で関数に引数を渡したり、関数から結果を返すことを行わないということです。 このため、C言語関数は実際にC言語で書かれているものと仮定します。
C関数の作成と構築の基本規則を以下に示します。
pg_config --includedir-server
を使用して、使用中のシステム(もしくはユーザが実行するシステム)にてPostgreSQLサーバのヘッダファイルがインストールされた場所を見つけます。
PostgreSQLに動的にロードできるように独自コードをコンパイル/リンクする時には常に、特別なフラグが必要となります。 特定のオペレーティングシステムにおけるコンパイル/リンク方法については37.10.5を参照してください。
忘れずに37.10.1で説明した「マジックブロック」を共有ライブラリで定義してください。
メモリを割り当てる際、Cライブラリのmalloc
とfree
ではなく、PostgreSQLのpalloc
とpfree
を使用してください。
palloc
で割り当てられたメモリは各トランザクションの終わりに自動的に解放され、メモリリークを防ぎます。
memset
を使用して、構造体を必ずゼロクリアしてください(または最初の段階でpalloc0
を用いて割り当ててください)。
構造体の各フィールドを割り当てたとしても、ゴミの値を持つ整列用のパディング(構造体内の穴)があるかもしれません。
こうしないと、ハッシュインデックスやハッシュ結合をサポートすることが困難です。
ハッシュを計算するには、データ構造体内の有意なビットのみを取り出す必要があるためです。
プランナはまた時折ビット単位の等価性を用いて定数の比較を行います。
このため論理的にな値がビット単位で等価でない場合に望まない計画になってしまう可能性があります。
ほとんどのPostgreSQLの内部型はpostgres.h
に宣言されています。
一方、関数管理インタフェース(PG_FUNCTION_ARGS
など)はfmgr.h
で宣言されています。
したがって、少なくともこの2つのファイルをインクルードする必要があります。
移植性に関する理由により、postgres.h
をその他のシステムヘッダファイル、ユーザヘッダファイルよりも先にインクルードしておくことが最善です。
postgres.h
をインクルードすることはelog.h
、palloc.h
もインクルードすることになります。
オブジェクトファイルで定義されているシンボル名は、互いに、またはPostgreSQLサーバの実行ファイルで定義されているものと異なっている必要があります。 これに関するエラーが表示される場合は、関数名または変数名を変更する必要があります。
Cで書かれたPostgreSQLの拡張関数を使うためには、サーバが動的にロードできるように特別な方法でコンパイルとリンクを行う必要があります。 正確には共有ライブラリを作る必要があります。
本節の説明以上の詳しい情報はオペレーティングシステムのドキュメント、特にCコンパイラcc
とリンクエディタld
のマニュアルページを参照してください。
さらに、PostgreSQLのソースコードのcontrib
ディレクトリにいくつか実例があります。
しかし、もしこれらの例に頼るとPostgreSQLソースコードが利用できることに依存したモジュールが作られてしまいます。
共有ライブラリの作成は一般的に実行プログラムのリンクに類似しています。 まずソースファイルがオブジェクトファイルにコンパイルされ、そのオブジェクトファイル同士がリンクされます。 これらのオブジェクトファイルは位置独立なコード(PIC)として作られる必要があります。 それは概念的には、実行プログラムから呼び出される時にメモリの適当な場所に置くことができるということです (実行プログラム用として作られたオブジェクトファイルはそのようにはコンパイルされません)。 共有ライブラリをリンクするコマンドは実行プログラムのリンクと区別するための特別なフラグがあります(少なくとも理論上ではそのようになっています。システムによってはもっと醜い実際が見受けられます)。
次の例ではソースコードはfoo.c
ファイルにあると仮定し、foo.so
という共有ライブラリを作るとします。
中間のオブジェクトファイルは特別な記述がない限りfoo.o
と呼ばれます。
共有ライブラリは1つ以上のオブジェクトファイルを持つことができますが、ここでは1つしか使いません。
PICを作るためのコンパイラフラグは-fPIC
です。
共有ライブラリを作るコンパイラフラグは-shared
です。
gcc -fPIC -c foo.c gcc -shared -o foo.so foo.o
これはFreeBSDのバージョン3.0に適用されます。
PICを作るためのシステムコンパイラのコンパイラフラグは+z
です。
GCCを使う場合は-fPIC
です。
共有ライブラリのためのリンカフラグは-b
です。
したがって、以下のようになります。
cc +z -c foo.c
または
gcc -fPIC -c foo.c
そして
ld -b -o foo.sl foo.o
HP-UXは他のほとんどのシステムと異なり共有ライブラリに.sl
という拡張子を使います。
PICを作るためのコンパイラフラグは-fPIC
です。
共有ライブラリを作るコンパイラフラグは-shared
です。
完全な例は下記のようになります。
cc -fPIC -c foo.c cc -shared -o foo.so foo.o
例を以下に示します。 開発者用ツールがインストールされていることが前提です。
cc -c foo.c cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o
PICを作るためのコンパイラフラグは-fPIC
です。
ELFシステムでは-shared
コンパイラフラグを使用して共有ライブラリをリンクします。
より古い非ELFシステムではld -Bshareable
が使われます。
gcc -fPIC -c foo.c gcc -shared -o foo.so foo.o
PICを作成するためのコンパイラフラグは-fPIC
です。
共有ライブラリをリンクするにはld -Bshareable
を使用します。
gcc -fPIC -c foo.c ld -Bshareable -o foo.so foo.o
PICを作るためのコンパイラフラグはSunコンパイラでは-KPIC
で、GCCでは-fPIC
です。
共有ライブラリをリンクするためには、どちらのコンパイラでもコンパイラオプションは-G
で、GCCの場合、代わりに-shared
オプションを使うこともできます。
cc -KPIC -c foo.c cc -G -o foo.so foo.o
もしくは
gcc -fPIC -c foo.c gcc -G -o foo.so foo.o
これがあまりに難しいようであれば、GNU Libtoolの使用を検討すべきです。 これはプラットフォームの違いを、統一されたインタフェースで判らないようにします。
これで完成した共有ライブラリファイルはPostgreSQLにロードすることができます。
CREATE FUNCTION
コマンドにファイル名を指定する時には、中間オブジェクトファイルではなく共有ライブラリファイルの名前を与えてください。
システムの標準共有ライブラリ用の拡張子(通常.so
あるいは.sl
)はCREATE FUNCTION
で省略することができ、そして移植性を最も高くするため通常は省略されます。
サーバがライブラリファイルをどこに見つけるかに関しては37.10.1を見直してください。
複合型ではCの構造体のような固定のレイアウトがありません。 複合型のインスタンスはNULLフィールドを持つことができます。 さらに、複合型で継承階層の一部であるものは、同じ継承階層の他のメンバとは異なるフィールドを持つこともできます。 そのため、PostgreSQLはC言語から複合型のフィールドにアクセスするための関数インタフェースを提供します。
以下のような問い合わせに答える関数を書こうとしていると仮定します。
SELECT name, c_overpaid(emp, 1500) AS overpaid FROM emp WHERE name = 'Bill' OR name = 'Sam';
Version 1呼び出し規約を使用すると、c_overpaid
は以下のように定義できます。
#include "postgres.h" #include "executor/executor.h" /* GetAttributeByName()用 */ PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(c_overpaid); Datum c_overpaid(PG_FUNCTION_ARGS) { HeapTupleHeader t = PG_GETARG_HEAPTUPLEHEADER(0); int32 limit = PG_GETARG_INT32(1); bool isnull; Datum salary; salary = GetAttributeByName(t, "salary", &isnull); if (isnull) PG_RETURN_BOOL(false); /* この他、salaryがNULLの場合用にPG_RETURN_NULL()を行った方が良いでしょう */ PG_RETURN_BOOL(DatumGetInt32(salary) > limit); }
GetAttributeByName
は、指定された行から属性を返す、PostgreSQLシステム関数です。
これには3つの引数があります。
それらは、関数に渡されたHeapTupleHeader
型の引数、求められた属性の名前、属性がNULLであるかどうかを通知する返りパラメータです。
GetAttributeByName
は適切なDatumGet
マクロを使用して適切なデータ型に変換可能なXXX
()Datum
型の値を返します。
このNULLフラグが設定されている場合、戻り値の意味がないことに注意し、この結果で何かを行おうとする前に常に、NULLフラグを検査してください。
対象列を名前ではなく列番号で選択するGetAttributeByNum
もあります。
下記のコマンドでc_overpaid
関数をSQLで宣言します。
CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
AS 'DIRECTORY
/funcs', 'c_overpaid'
LANGUAGE C STRICT;
入力引数がNULLかどうかを検査する必要がないようにSTRICT
を使用していることに注意してください。
C言語関数から行もしくは複合型の値を返すために、複合型の複雑な作成のほとんどを隠蔽するマクロや関数を提供する、特別なAPIを使用することができます。 このAPIを使用するためには、ソースファイルで以下をインクルードする必要があります。
#include "funcapi.h"
複合型のデータ値(以降「タプル」と記す)を作成する2つの方法があります。
Datum値の配列から作成する方法、もしくはタプルのある列の型の入力変換関数に渡すことができるC文字列の配列から作成することです。
どちらの方法でも、まずタプル構造体用のTupleDesc
記述子を入手、あるいは作成しなければなりません。
Datumを使用する場合は、TupleDesc
をBlessTupleDesc
に渡し、各行に対してheap_form_tuple
を呼び出します。
C文字列を使用する場合は、TupleDesc
をTupleDescGetAttInMetadata
に渡し、各行に対して BuildTupleFromCStrings
を呼び出します。
タプルの集合を返す関数の場合、この設定段階を最初の関数呼び出しで一度にまとめて行うことができます。
必要なTupleDesc
の設定用の補助用関数がいくつかあります。
ほとんどの複合型を返す関数での推奨方法は、以下の関数を呼び出し、呼び出し元の関数自身に渡されるfcinfo
構造体と同じものを渡すことです。
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo, Oid *resultTypeId, TupleDesc *resultTupleDesc)
(これにはもちろん、version 1呼び出し規約を使用していることが必要です。)
resultTypeId
をNULL
とすることも、ローカル変数のアドレスを指定して関数の戻り値型のOIDを受け取ることができます。
resultTupleDesc
はローカルなTupleDesc
変数のアドレスでなければなりません。
結果がTYPEFUNC_COMPOSITE
かどうかを確認してください。
TYPEFUNC_COMPOSITE
であった場合、resultTupleDesc
には必要なTupleDesc
が格納されています。
(TYPEFUNC_COMPOSITE
ではなかった場合、「レコード型を受け付けない文脈でレコードを返す関数が呼び出されました」というエラーを報告することができます。)
get_call_result_type
は、多様性関数の結果の実際の型を解決することができます。
ですので、複合型を返す関数だけではなく、スカラの多様結果を返す関数でも有意です。
resultTypeId
出力は主にスカラの多様結果を返す関数で有意です。
get_call_result_type
は、get_expr_result_type
と似たような関数で、関数呼び出しで想定される出力型を式のツリー構造として解決します。
関数自身以外から結果型を決定したい場合に、これを使用することができます。
また、get_func_result_type
という関数もあります。
これは関数のOIDが利用できる場合にのみ使用することができます。
しかし、これらの関数は、record
型を返すものと宣言された関数では使用できません。
また、get_func_result_type
は多様型を解決することができません。
したがって、優先してget_call_result_type
を使用すべきです。
古く、廃止予定のTupleDesc
を入手するための関数を以下に示します。
TupleDesc RelationNameGetTupleDesc(const char *relname)
これを指名したリレーションの行型用のTupleDesc
を取り出すために使用してください。
また、
TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)
これを型のOIDに基づいてTupleDesc
を取り出すために使用してください。
これは、基本型もしくは複合型のTupleDesc
を取り出すために使用可能です。
これはrecord
を返す関数ではうまく動作しません。
また、多様型を解決することもできません。
TupleDesc
を獲得した後に、Datumを使用する場合は以下を呼び出してください。
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
C文字列を使用する場合は以下を呼び出してください。
AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)
集合を返す関数を作成する場合は、これらの関数の結果をFuncCallContext
構造体に格納してください。
それぞれtuple_desc
とattinmeta
を使用します。
Datumを使用する場合は、ユーザデータをDatum形式に格納したHeapTuple
を構築するために以下を使用します。
HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)
C文字列を使用する場合は、ユーザデータをC文字列形式に格納したHeapTuple
を構築するために以下を使用します。
HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
values
は行の各属性を1要素としたC文字列の配列です。
各C文字列は、属性のデータ型用の入力関数が受け付け可能な形式でなければなりません。
属性の値をNULL値として返すためには、values
配列の対応するポインタにNULL
を設定してください。
この関数は返す行それぞれに対して繰り返し呼び出す必要があります。
関数から返すタプルを構築し終わったら、それをDatum
に変換しなければなりません。
以下を使用して、HeapTuple
を有効なDatumに変換してください。
HeapTupleGetDatum(HeapTuple tuple)
単一行のみを返すのであれば、このDatum
を直接返すことができます。
さもなくば、集合を返す関数における現在の戻り値として使用することができます。
次節に例を示します。
C言語関数から集合(複数行)を返すには2つ選択肢があります。 一つは、ValuePerCallモードと呼ばれる方法で、集合を返す関数が繰り返し呼び出され(毎回同じ引数を渡します)、返す行がなくなるまで呼び出しごとに1つの新しい行を返し、返す行がなくなったらNULLを返します。 したがって、集合を返す関数(SRF)は、呼び出し間に十分な状態を保存し何をしていたかを記憶して、呼び出しの度に次の項目を返す必要があります。 もう一つは、Materializeモードと呼ばれる方法で、集合を返す関数は結果全体を含むタプルストアオブジェクトを埋めて返します。 結果全体に対して1つの呼び出しだけが発生し、呼び出し間の状態は必要ありません。
ValuePerCallモードを使用する場合、問い合わせが完全に実行される保証はないことに注意してください。
つまり、LIMIT
などのオプションがあるため、全ての行をフェッチする前に、エクゼキュータが集合を返す関数の呼び出しを中止することがあります。
これは、実行されない可能性があるため、最後の呼び出しでクリーンアップ活動を実行するのは安全ではないことを意味します。
ファイル記述子などの外部リソースにアクセスする必要がある関数には、Materializeモードを使用することをお勧めします。
本節の残りの部分では、ValuePerCallモードを使用する集合を返す関数で一般に使用される補助マクロのセット(ただし、使用は必須ではありませんが)について説明します。
Materializeモードの詳細については、src/backend/utils/fmgr/README
を参照してください。
また、PostgreSQLソース配布物内のcontrib
モジュールには、ValuePerCallとMaterializeモードの両方を使用する、集合を返す関数のより多くの例があります。
ここで説明するValuePerCallサポートマクロを使用するには、funcapi.h
をインクルードします。
これらのマクロは、複数の呼び出しにわたって保存する必要がある状態を含むFuncCallContext
構造体が備わっています。
集合を返す関数内では、fcinfo->flinfo->fn_extra
は、呼び出し間でFuncCallContext
へのポインタを保持するために使用されます。
マクロは、最初の使用時に自動的にそのフィールドを埋め、その後の使用時に同じポインタを見つけることを期待します。
typedef struct FuncCallContext { /* * 既に行われた呼び出しの回数。 * * SRF_FIRSTCALL_INIT()によってcall_cntrが0に初期化され、 * SRF_RETURN_NEXT()が呼び出される度に増分されます。 */ uint64 call_cntr; /* * 省略可能 : 呼び出しの最大数 * * max_callsは、便宜上用意されているだけで、設定は省略可能です。 * 設定されていなければ、関数が終了したことを知るための別の方法を * 用意する必要があります。 */ uint64 max_calls; /* * 省略可能 : 様々なユーザによるコンテキスト情報へのポインタ * * user_fctxは、関数の呼び出し間の任意のコンテキスト情報を * 取得するためのユーザ独自の構造へのポインタとして使用されます。 */ void *user_fctx; /* * 省略可能 : 属性型入力メタ情報を含んだ構造体へのポインタ * * attinmeta はタプル(つまり複合データ型)を返す際に使用され、 * 基本データ型を返す場合には必要ありません。 * BuildTupleFromCStrings()を使用して返されるタプルを作成する場合にのみ必要です。 */ AttInMetadata *attinmeta; /* * 複数の呼び出しで必要とされる構造体に使われるメモリコンテキスト * * multi_call_memory_ctxは、SRF_FIRSTCALL_INIT()によってに設定され、 * SRF_RETURN_DONE()がクリーンアップの際に使用します。 * これはSRFの複数呼び出しで再利用される全てのメモリ用に最も適切なメモリコンテキストです。 */ MemoryContext multi_call_memory_ctx; /* * 省略可能: タプル説明を含む構造体へのポインタ。 * tuple_descはタプル(つまり複合データ型)を返す場合に使用され、BuildTupleFromCStrings() * ではなくheap_form_tuple()を使用してタプルを作成する場合にのみ必要です。 * 通常ここに格納されるTupleDescは最初にBlessTupleDesc()を最初に実行したものでなければなり * ません。 */ TupleDesc tuple_desc; } FuncCallContext;
この基盤を使用して、SRFが使用するマクロは以下の通りです。
SRF_IS_FIRSTCALL()
これを使用して、関数が初めて呼び出されたのか、2回目以降に呼び出されたのかを判別します。 最初の呼び出し(のみ)で、
SRF_FIRSTCALL_INIT()
を呼び出し、FuncCallContext
を初期化します。
最初の呼び出しを含むすべての呼び出しで、
SRF_PERCALL_SETUP()
を呼び出し、FuncCallContext
を使用するように設定します。
現在の呼び出しで返すべきデータが関数にある場合は、次を使用します。
SRF_RETURN_NEXT(funcctx, result)
を使用して、そのデータを呼び出し側に返します。
(先に説明した通り result
はDatum
型、つまり1つの値またはタプルである必要があります。)
最後に、関数がデータを返し終わったら、
SRF_RETURN_DONE(funcctx)
を使用してSRFを片付け、終了します。
SRFの呼び出し時に現行になっているメモリコンテキストは一時的なコンテキストで、各呼び出しの間に消去されます。
つまりpalloc
を使用して割り当てたもののすべてをpfree
する必要はありません。
これらはいずれ消去されるものだからです。
しかし、データ構造体を複数の呼び出しに渡って使用するように割り当てる場合は、どこか別の場所に置いておく必要があります。
multi_call_memory_ctx
によって参照されるメモリコンテキストは、SRFの実行が終わるまで使用可能にしなければならないデータの保管場所として適しています。
つまり、ほとんどの場合、最初の呼び出しのセットアップ中にmulti_call_memory_ctx
へ切り替える必要があるということです。
funcctx->user_fctx
を使用して、このような複数の呼び出しに渡るデータ構造体へのポインタを保持します。
(multi_call_memory_ctx
に配置したデータは、問い合わせが終了すると自動的に削除されるので、そのデータを手動で解放する必要はありません。)
関数の実引数は呼出しの間変わらないままですが、一時的なコンテキストで引数の値をTOAST解除した場合には(これは通常、PG_GETARG_
マクロにより透過的に行なわれます)、TOAST解除されたコピーが各サイクルで解放されます。
従って、xxx
user_fctx
内のその値への参照を保持する場合には、TOAST解除した後にmulti_call_memory_ctx
にそれらをコピーするか、その値をTOAST解除するのはそのコンテキストの中だけであること確実にしなければなりません。
完全な疑似コードの例を示します。
Datum my_set_returning_function(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; Datum result;further declarations as needed
if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* 一度限りのセットアップコードがここに入ります: */user code
if returning composite
build TupleDesc, and perhaps AttInMetadata
endif returning composite
user code
MemoryContextSwitchTo(oldcontext); } /* 毎回実行するセットアップコードがここに入ります: */user code
funcctx = SRF_PERCALL_SETUP();user code
/* これは、終了したかどうかをテストする方法の1つです: */ if (funcctx->call_cntr < funcctx->max_calls) { /* ここで、別の項目を返します: */user code
obtain result Datum
SRF_RETURN_NEXT(funcctx, result); } else { /* これで項目を返し終わりました。 その事実を報告します。 */ /* (ここにクリーンアップコードを置く誘惑に抵抗してください。) */ SRF_RETURN_DONE(funcctx); } }
複合型を返す単純なSRFの完全な例は以下の通りです。
PG_FUNCTION_INFO_V1(retcomposite); Datum retcomposite(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int call_cntr; int max_calls; TupleDesc tupdesc; AttInMetadata *attinmeta; /* 関数の最初の呼び出し時にのみ実行 */ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; /* 呼び出し間で永続化する関数コンテキストを作成 */ funcctx = SRF_FIRSTCALL_INIT(); /* 複数関数呼び出しに適切なメモリコンテキストへの切り替え */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* 返されるタプルの合計数 */ funcctx->max_calls = PG_GETARG_UINT32(0); /* 結果型用のタプル記述子を作成 */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); /* * 後で未加工のC文字列からタプルを作成するために必要となる * 属性メタデータの生成 */ attinmeta = TupleDescGetAttInMetadata(tupdesc); funcctx->attinmeta = attinmeta; MemoryContextSwitchTo(oldcontext); } /* 全ての関数呼び出しで実行 */ funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; attinmeta = funcctx->attinmeta; if (call_cntr < max_calls) /* 他にも送るものがある場合 */ { char **values; HeapTuple tuple; Datum result; /* * 返すタプルを構築するためのvalues配列を用意します。 * これは、後で適切な入力関数で処理される * C文字列の配列でなければなりません。 */ values = (char **) palloc(3 * sizeof(char *)); values[0] = (char *) palloc(16 * sizeof(char)); values[1] = (char *) palloc(16 * sizeof(char)); values[2] = (char *) palloc(16 * sizeof(char)); snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1)); snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1)); snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1)); /* タプルの作成 */ tuple = BuildTupleFromCStrings(attinmeta, values); /* タプルをdatumに変換 */ result = HeapTupleGetDatum(tuple); /* クリーンアップ(これは必須ではありません) */ pfree(values[0]); pfree(values[1]); pfree(values[2]); pfree(values); SRF_RETURN_NEXT(funcctx, result); } else /* 何も残っていない場合 */ { SRF_RETURN_DONE(funcctx); } }
以下にこの関数をSQLで宣言する一例を示します。
CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer);
CREATE OR REPLACE FUNCTION retcomposite(integer, integer)
RETURNS SETOF __retcomposite
AS 'filename
', 'retcomposite'
LANGUAGE C IMMUTABLE STRICT;
他にも以下のようにOUTパラメータを使用する方法もあります。
CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
OUT f1 integer, OUT f2 integer, OUT f3 integer)
RETURNS SETOF record
AS 'filename
', 'retcomposite'
LANGUAGE C IMMUTABLE STRICT;
この方法では、関数の出力型は形式上無名のrecord
型になることに注意してください。
C言語関数は、37.2.5で説明されている多様型を受け付ける、または返すように宣言することができます。
多様関数の詳細な説明は37.2.5を参照してください。
関数の引数もしくは戻り値が多様型として定義される時、関数の作成者は前もって呼び出しにおけるデータ型や返すべきデータ型が何であるかを知ることはできません。
Version-1 C関数で引数の実データ型と、返すべきと想定された型を発見できるための2つのルーチンがfmgr.h
に用意されています。
このルーチンはget_fn_expr_rettype(FmgrInfo *flinfo)
とget_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
という名前です。
これらは結果もしくは引数型のOIDを返します。
ただし、もし情報が利用できなければInvalidOid
を返します。
flinfo
構造体は通常fcinfo->flinfo
としてアクセスされます。
argnum
パラメータは0から始まります。
また、get_fn_expr_rettype
の代わりにget_call_result_type
を使用することもできます。
また、variadic変数が配列に吸収されたかどうかを判定するために使用できるget_fn_expr_variadic
があります。
そのような吸収はvariadic関数が普通の配列型をとる場合に必ず起こりますので、これは特にVARIADIC "any"
の場合に有用です。
例えば、任意の型の単一要素を受け付け、その型の1次元配列を返す関数を考えてみます。
PG_FUNCTION_INFO_V1(make_array); Datum make_array(PG_FUNCTION_ARGS) { ArrayType *result; Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 0); Datum element; bool isnull; int16 typlen; bool typbyval; char typalign; int ndims; int dims[MAXDIM]; int lbs[MAXDIM]; if (!OidIsValid(element_type)) elog(ERROR, "could not determine data type of input"); /* 与えられた要素がNULLかどうか注意しつつ、要素を取り出します。*/ isnull = PG_ARGISNULL(0); if (isnull) element = (Datum) 0; else element = PG_GETARG_DATUM(0); /* 次元数は1 */ ndims = 1; /* 要素を1つ */ dims[0] = 1; /* 下限は1 */ lbs[0] = 1; /* この要素型に関する必要情報を取り出す。 */ get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); /* ここで配列を作成 */ result = construct_md_array(&element, &isnull, ndims, dims, lbs, element_type, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result); }
以下のコマンドはSQLでmake_array
関数を宣言します。
CREATE FUNCTION make_array(anyelement) RETURNS anyarray
AS 'DIRECTORY
/funcs', 'make_array'
LANGUAGE C IMMUTABLE;
C言語関数でのみ使用できる多様性の変異体があります。
"any"
型のパラメータを取るように宣言できます。
(この型名は、SQL予約語でもあるため二重引用符で括らなくてはならないことに注意してください。)
これは、他の"any"
引数が同じ型になることを強要することも、関数の結果型の決定を支援することもない点を除いて、anyelement
のように動作します。
C言語関数は最終パラメータがVARIADIC "any"
であるように宣言可能です。
これは任意の型の1つ以上の実引数と一致します(同じ型である必要はありません)。
これらの引数は、通常のvariadic関数で起こったように、配列の中にまとめられません。
それらは単に別々に関数に渡されるだけです。
PG_NARGS()
マクロと上に記載したメソッドは、この機能を使用するときに実際の引数とその型を決定するため使用されなければなりません。
また、こうした関数のユーザは、その関数呼び出しにおいて、関数が配列要素を分離した引数として扱うだろうという予想のもとでVARIADIC
キーワードを良く使用するかもしれません。
関数自身は必要ならば、get_fn_expr_variadic
を実行した後で、実引数がVARIADIC
付きであることを検出した場合に、その動作を実装しなければなりません。
アドインはLWLocks(軽量ロック)とサーバ起動時に共有メモリの割り当てを保持することができます。
shared_preload_librariesで指定して、こうしたアドインの共有ライブラリを事前にロードしなければなりません。
共有メモリは、その_PG_init
関数で以下を呼び出すことで保持されます。
void RequestAddinShmemSpace(int size)
LWLocksはその_PG_init
関数で以下を呼び出すことで保持されます。
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
num_lwlocks
個のLWLockの配列がtranche_name
という名前で確実に利用できるようにします。
この配列へのポインタを得るにはGetNamedLWLockTranche
を使ってください。
競合状態の可能性を防止するために、割り当てられた共有メモリへの接続やその初期化時に、以下のように各バックエンドでAddinShmemInitLock
軽量ロックを使用しなければなりません。
static mystruct *ptr = NULL; if (!ptr) { bool found; LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); ptr = ShmemInitStruct("my struct name", size, &found); if (!found) { initialize contents of shmem area; acquire any requested LWLocks using: ptr->locks = GetNamedLWLockTranche("my tranche name"); } LWLockRelease(AddinShmemInitLock); }
以下のガイドラインに従うことで、PostgreSQLの拡張を構築するためC++モードのコンパイラを利用できます。
バックエンドからアクセスされる関数はすべてバックエンドに対してCインタフェースを提供しなければなりません。
このC関数はC++関数を呼びだすことができます。
例えば、バックエンドからアクセスされる関数にはextern C
リンクが必要です。
これはバックエンドとC++コードの間でポインタとして渡される関数にも必要です。
適切な解放メソッドを使ってメモリを解放してください。
例えば、ほとんどのバックエンドメモリはpalloc()
で確保されますので、pfree()
を使って解放してください。
この場合にC++のdelete()
を使うと失敗するでしょう。
例外がCコードへ伝播しないようにしてください(extern C
関数すべての最上位ですべての例外を捕捉するブロックを使ってください)。
メモリ不足のようなイベントにより例外が発生する可能性がありますので、C++コードが何も例外を発生させない場合であっても、これは必要です。
例外はすべて捕捉しなければなりません。
そして適切なエラーをCインタフェースに渡してください。
可能であれば、例外を完全に除去できるように-fno-exceptions
を付けてC++をコンパイルしてください。
その場合、例えばnew()
で返されるNULLの検査など、C++コード内で失敗の検査を行わなければなりません。
C++コードからバックエンド関数を呼び出す場合には、C++呼び出しスタック内にC言語互換構造体(POD)のみが含まれていることを確認してください。
バックエンドのエラーは、非PODオブジェクトを持つC++呼び出しスタックを適切に戻すことができない、長距離longjmp()
を生成しますので、これは必要です。
まとめると、バックエンドとやりとりするための壁の役割を担うextern C
関数の背後にC++コードを配置して、例外、メモリ、呼び出しスタックそれぞれの漏れを避けるのが最善です。