◆VC++ 2005環境での PostgreSQL - C言語関数開発(1)

[ ホーム ] [ 上へ ] [ PostgreSQL - C言語関数(2) ]

PostgreSQLはソースが公開されているのでいざとなれば、それを追っかけてバグを つぶせたりできるのでとっても有難いです。また、ない機能でほしい機能は自身で組み込む こともできます。普通はSQL関数、PL/pgSQLでやりたいことはできるのですが、よりシステム寄りな ことやどうしてもこれらでは実現できないことはC言語関数で処理してます。 ExtLinkもC言語関数以外では同機能はできません。

ところでExtLinkの開発環境ですが表題にもあるように VC++ 2005です。もともとPostgreSQLはUnix系での運用がメインでWindows環境での開発には いろいろ問題があります。C言語関数の開発手順はドキュメントに例題も加えて割りと詳しく 記載されていると思いますが、VC++ の開発環境ではまったくうまくいきません。 さらにWindowsがプラットフォームである運用の場合はさらに困難になります。 その解決例を正しくないこともあるかもしれませんが書いてみます。

これからの内容は

OS : Windows XP, Windows 2000
DB : PostgreSQL 8.3  (Windowsバイナリ版)
開発環境 : VC++ 2005 

を前提にしています。

・前準備

  1. Visual Studioを起動してプロジェクトを作成します。
    プロジェクトの種類  : Visual C++
    テンプレート        : Win32プロジェクト
    プロジェクト名      : CFunc (任意)
    
    でOKをクリックするとウィザードが起動するので、次へをクリック。
    アプリケーションの設定ダイアログで以下のように設定してください。
    アプリケーションの種類  : DLL(D)
    追加のオプション        : 空のプロジェクト(E)にチェック
    
    をして、完了ボタンを押す
  2. ツールバーのプロジェクト(P)→CFuncのプロパティ(P)でプロパティページを表示します。
    構成(C) : すべての構成を選択。
    左ペインの構成プロパティ - 全般を選択
    
    プロジェクトの既定値 : 構成の種類  -  ダイナミックライブラリ(dll)
                                              (既に選択されています)
                        : 文字セット  -  マルチバイト文字セット
                            (他はそのまま)
    
    でOKボタンを押す。
  3. ツールバーのプロジェクト(P)→新しい項目の追加(W)からC++ファイルを作成します。
    カテゴリ(C)  : Visual C++
    テンプレート : C++ファイル(cpp)
    ファイル名   : CFunc(任意)
    
    で追加ボタンを押す。
  4. ソリューションエクスプローラでCFuncプロジェクトを選択して、プロジェクト(P)→CFuncの プロパティ(P)でプロパティページを表示します。
    4a.構成(C) : すべての構成
                構成のプロパティ - 
                                 +- C/C++
                                        +- 全般
    
    を選択してください。
    右ペインの追加のインクルードディレクトリで以下の インクルードパスを通しておいてください。
    C:\Program Files\PostgreSQL\8.3\include\server
    C:\Program Files\PostgreSQL\8.3\include\server\port\win32
    C:\Program Files\PostgreSQL\8.3\include\server\port\win32_msvc
    
    4b.構成(C) : すべての構成
            構成のプロパティ - 
                             +- C/C++
                                    +- 詳細
    
    を選択してください。
    右ペインのコンパイル言語の選択Cコードでコンパイル(T/C)に変更してください。
    最後にOKボタンを押して確定すると実装準備ができました。

・ドキュメントによると

ドキュメントでは関数の呼び出し規約でVersion0規約と Version1規約がありますが、前者は古く、目的の処理ができない場合があるので以後は Version1規約で記述します。
まず、PostgreSQL 8.3.4のドキュメント34.9以降で挙げられている行セットを返すtest_func関数を ドキュメントの記述を参考に記述すると以下のようになります。

//CFunc.cpp///////////////////////////////////////////////////

#include "postgres.h"
#include "funcapi.h" //fmgr.hはインクルード済み

#ifdef PG_MODULE_MAGIC
    PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(test_func);

Datum test_func(PG_FUNCTION_ARGS)
{
    FuncCallContext  *pFcctx;    
    MemoryContext    oldmemoryctx;    
    TupleDesc        tupledesc;    
    AttInMetadata    *pAttinmeta;    
    Datum            result;

    if (SRF_IS_FIRSTCALL())    
    {
        pFcctx = SRF_FIRSTCALL_INIT();    
        oldmemoryctx = MemoryContextSwitchTo(pFcctx->multi_call_memory_ctx);    
        pFcctx->max_calls = PG_GETARG_UINT32(0);    //(A)

        if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
        {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED)));    
        }

        pAttinmeta = TupleDescGetAttInMetadata(tupledesc);    
        pFcctx->attinmeta = pAttinmeta;    

        MemoryContextSwitchTo(oldmemoryctx);
    }

    pFcctx = SRF_PERCALL_SETUP();    

    if (pFcctx->call_cntr < pFcctx->max_calls)    
    {
        int       i;    
        char      *pSrc;    
        char      **ppValues = palloc(2 * sizeof(char *));
        HeapTuple htuple;    

        for (i=0; i<2; i++)    
        {
            ppValues[i] = palloc(16 * sizeof(char));    
            pSrc = DatumGetCString(
                             DirectFunctionCall1(textout, //(B)
                                                 PointerGetDatum(PG_GETARG_TEXT_P(i+1))));

            if (strcpy_s(ppValues[i], 16, pSrc))    
                ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY)));    
        }

        htuple = BuildTupleFromCStrings(pFcctx->attinmeta, ppValues);    
        result = HeapTupleGetDatum(htuple);    

        SRF_RETURN_NEXT(pFcctx, result);    
    } else    
    {
        SRF_RETURN_DONE(pFcctx);
    }
}

test_funcの内容は(B)の第二、第三引数で指定した文字列を1レコードとして、 (A)の第一引数で指定した数だけのレコード数を出力する単純なものです。

SELECT * FROM test_func(3, 'test1', 'test2') AS T1(f1 text, f2 text);
結果
f1 (text) f2 (text)
test1 test2
test1 test2
test1 test2

となるはずですが現状コンパイルさえ通りません。 この結果が得られるまで長い道のりが始まります。