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

[ 上へ ] [ PostgreSQL - C言語関数(3) ]

・デバッグ

では、一つ一つデバッグしていきます。ツールバーの表示(V)→エラー一覧(I)で エラー内容を見てみると

fatal error C1189: #error : 
   "Postgres uses 32 bit time_t - add #define _USE_32BIT_TIME_T on Windows"
    c:\program files\postgresql\8.3\include\server\pg_config_os.h	55

となっているのでpg_config_os.hを見てみると

#if defined(WIN32_ONLY_COMPILER) && !defined(FRONTEND)
#ifndef _USE_32BIT_TIME_T
#error "Postgres uses 32 bit time_t - add #define _USE_32BIT_TIME_T on Windows"
#endif
#endif

ということで_USE_32BIT_TIME_TをCFunc.cppの最初に定義します。

//CFunc.cpp////////////////////////////////////////////////////////
#define _USE_32BIT_TIME_T

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

再度ビルドすると、エラーや警告がごっそり吐き出されるはずですが、まずはエラーから。

error C2065: 'textout' : 
     定義されていない識別子です。d:\xxx\projects\cfunc\cfunc\cfunc.cpp 62

textoutはPostgreSQLの内部関数ですが、結論からいうと、 これを外部で使えるように宣言されたヘッダファイルがあるのでこれをインクルードします。

//CFunc.cpp////////////////////////////////////////////////////////
#define _USE_32BIT_TIME_T

#include "postgres.h"
#include "funcapi.h"        //fmgr.hはインクルード済み
#include "utils/builtins.h" //Postgres内部関数textout使用のため
・
・

警告はとりあえずペンディングにしてビルドすると、わけのわからないエラーがさらに増えます。

error LNK2019: 未解決の外部シンボル _end_MultiFuncCall が
                      関数 _test_func で参照されました。     CFunc.obj

error LNK2019: 未解決の外部シンボル _BuildTupleFromCStrings が
                      関数 _test_func で参照されました。     CFunc.obj

・

error LNK2019: 未解決の外部シンボル _pg_detoast_datum が
                      関数 _test_func で参照されました。	    CFunc.obj

error LNK2001: 外部シンボル "__imp__CurrentMemoryContext" は
                      未解決です。	                    CFunc.obj
・

error LNK2019: 未解決の外部シンボル _MemoryContextSwitchTo が
                      関数 _test_func で参照されました。     CFunc.obj

一部を抜粋しましたが、ここでも警告はペンディングにしてエラーだけ見ると今度は リンカエラーです。上記のエラーメッセージで表示された関数は ここで使われているものですが、 「外部の実行ファイルが見つからんよ。」とお叱りを受けています。これらの関数は
C:\Program Files\PostgreSQL\8.3\bin\postgres.exe
内にあり、ライブラリファイルは
C:\Program Files\PostgreSQL\8.3\bin\postgres.lib
なので確認してください。 (サーバ稼動に重要なファイルなので触らないでください。)
ということで、#pragmaプリプロセッサでリンクします。

その前に、postgres.libがあるフォルダへのパスが通ってないので 通しておきます。
ツールバーのプロジェクト(P)→CFuncのプロパティ(P)からプロパティページを表示します。

構成(C) : すべての構成
構成プロパティ 
              +-リンカ
                       +-全般
を選択して右ペインの追加のライブラリディレクトリC:\Program Files\PostgreSQL\8.3\lib

を加えてください。 パスが通ったので以下でライブラリをリンクしてください。

//CFunc.cpp////////////////////////////////////////////////////////
#define _USE_32BIT_TIME_T

#include "postgres.h"
#include "funcapi.h         //fmgr.hはインクルード済み
#include "utils/builtins.h" //Postgres内部関数textout使用のため

//ライブラリリンク
#pragma comment(lib, "postgres.lib")

・
・

プロパティページの構成プロパティ - リンカ - 入力に追加の依存ファイルが あるので、ここに記述してもいいのですが、あとで発生する問題の説明のために #pragmaで処理しました。
最後にビルドしてください。これでとりあえず警告はでるもののビルドはできたはずです。

・試験実行

xxxx\Projects\CFunc\debug内に実行ファイルCFunc.dllが 出来上がっているのでサーバに組み込んでみます。

  1. オブジェクトファイル(CFunc.dll)を指定位置にコピーします。その指定位置ですが、 ドキュメントの34章 C言語関数の34.9.1動的ロードで

    ---------------------------------------------------------------------------
    CREATE FUNCTIONコマンドで与えられた名前に基づいて、共有オブジェクト ファイルの場所を見つける際に以下のアルゴリズムが使用されます。


    名前が$libdirという文字列から始まる場合、その部分はPostgreSQLパッケージのライブラリディレクトリ で置き換えられます。このディレクトリはビルド時に決定されます。
    ---------------------------------------------------------------------------

    とあります。$libdirはWindows版のPostgreSQLでは C:\Program Files\PostgreSQL\8.x\libに当たるのでここに コピーします。ビルドするたびにコピーするのは面倒なので ビルド後のイベントで自動的にコピーするようにしてしまいます。

    ツールバーのプロジェクト(P)→CFuncのプロパティ(P)でプロパティページのダイアログを表示します。
    構成(C) : すべての構成
    構成プロパティ
                +-ビルドイベント
                             +-ビルド後のイベント
    
    を選択し、コマンドライン
    copy $(TargetPath) C:\Progra~1\PostgreSQL\8.3\lib
    と記述しOKボタンを押します。これでビルドするたびにPostgreSQLのライブラリフォルダにコピーされます。
  2. (2).次にCREATE FUNCTIONでユーザ定義関数を作成します。発行するSQLコマンドは
    CREATE OR REPLACE FUNCTION test_func(int, text, text)
    RETURNS SETOF record AS
        '$libdir/CFunc','test_func'
    LANGUAGE C STRICT;     
    
    です。関数名のtest_funcは任意なのでお好みの名前に変えてください。pgAdminから発行すると楽ですが、 インストールされてない方のためにコンソールから発行します。

    メモ帳などから上記SQLコマンドを記述してC:\temp\CFunc.sql(任意)として保存します。 コンソールから
    C:\>cd \Program Files\PostgreSQL\8.3\bin [リターン]
    
    C:\Program Files\PostgreSQL\8.3\bin>
    psql -h localhost -U postgres -d データベース名 -f C:\temp\CFunc.sql [リターン]
    
    を実行してください。(詳しくはpsql.exeのドキュメントをご覧ください。)

ERROR: could not find function "test_func" in file "C:/Program Files/PostgreSQL/8.3/lib/CFunc.dll"

こんなエラーが表示されてCREATE FUNCTIONがうまくいかないと思います。

・マクロの確認/修正

<BUILDING_DLLスイッチ>

上記のエラーメッセージは「CFunc.dllの中にtest_funcなんて関数はないよ。」と 言っています。 そんなはずはないです。ちゃんとコーディングしてビルドもできました。
でもよく考え直してみると見つからないってことはエクスポートできてなくて外部からtest_funcが 見えないってことだろうと推察できそうなので

__declspec(dllexport) Datum test_func(PG_FUNCTION_ARGS)
{
    FuncCallContext *pFcctx;            
    MemoryContext oldmemoryctx;            
    TupleDesc tupledesc;            
        ・
        ・

とtest_funcの宣言部をエクスポートすればいけそうです。実際やってみたら できてしまいました。 できてしましました、と書きましたが実はこれ問題だそうです。 ここでこの関数を定義する前に PG_FUNCTION_INFO_V1(test_func)を定義したことを 思い出してください。これはtest_funcに関連した関数を定義したマクロです。 いろいろ調べてみるとこれもちゃんとエクスポートしないといけないらしいです。 ビルド時に以下の警告もでます。

warning C4273: 'pg_finfo_test_func' : dll リンクが一貫していません。
d:\windows\vs2005\projects\cfunc\cfunc\cfunc.cpp 20

引数にtest_functという関数名を設定しているので想像はできます。 とりあえず、fmgr.hで定義されているPG_FUNCTION_INFO_V1 のマクロの内容をみてみます。

#define PG_FUNCTION_INFO_V1(funcname) \           
extern PGDLLIMPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void); \
const Pg_finfo_record * \
CppConcat(pg_finfo_,funcname) (void) \
{ \
    static const Pg_finfo_record my_finfo = { 1 }; \
    return &my_finfo; \
} \
extern int no_such_variable

注目点はPGDLLIMPORTですが、これが__declspec(dllimport)と __declspec(dllexport)のスイッチになっていてこの定義がpg_config_os.h

#if defined(WIN32) || defined(__CYGWIN__)          

#if __GNUC__ && ! defined (__declspec)          
#error You need egcs 1.1 or newer for compiling!
#endif

#ifdef BUILDING_DLL
#define PGDLLIMPORT __declspec (dllexport) 
#else /* not BUILDING_DLL */
#define PGDLLIMPORT __declspec (dllimport) 
#endif
#else /* not CYGWIN, not MSVC, not MingW */

#define PGDLLIMPORT
#endif

となっています。BUILDING_DLLが定義されていたら __declspec(dllexport)になりますが、現状CFunc.cppには定義されていません。ということで __declspec(dllimport)になっています。

PGDLLIMPORTスイッチはあと、 PG_MODULE_MAGICマクロでも使われていますが、これもエクスポートしないといけなくて BUILDING_DLLスイッチを定義します。

//CFunc.cpp////////////これは使わないでください。////////////////////////////////
#define _USE_32BIT_TIME_T
#define BUILDING_DLL

#include "postgres.h"
#include "funcapi.h"        //fmgr.hはインクルード済み
#include "utils/builtins.h" //Postgres内部関数textout使用のため          

//ライブラリリンク
#pragma comment(lib, "postgres.lib")          

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(test_func);

PGDLLIMPORT Datum test_func(PG_FUNCTION_ARGS)
{
    FuncCallContext *pFcctx;          
    MemoryContext oldmemoryctx;          
・
・

これでビルドするとここの警告も解消されました。 これでPG_MODULE_MAGIC、PG_FUNCTION test_funcがエクスポートされたので SELECT * FROM test_func(xxx, xxx, xxx)を試したくなりますが、
BUILDING_DLLを定義したことによってエクスポートしたCFunc.dllを絶対に使用しないでください。 サーバが落ちます。相当悩みました。これを定義するだけでそんな重大なエラーが発生するのかと。

問題はpallocにありました。ドキュメントにはメモリの確保はpallocを使うように 書かれていて、test_funcにも使われています。pallocもマクロでpalloc.h

#define palloc(sz) MemoryContextAlloc(CurrentMemoryContext, (sz))

と定義されていて、さらにCurrentMemoryContext

extern PGDLLIMPORT MemoryContext CurrentMemoryContext;

とあります。MemoryContext型の変数CurrentMemoryContextが BUILDING_DLLを定義することによってエクスポートされています。 CurrentMemoryContextには無効なポインタでMemoryContextAllocが呼ばれているということで落ちる そうです。以上の理由でBUILDING_DLLを定義してはいけないことになるので、

//CFunc.cpp////////////////////////////////////////////////////////
#define _USE_32BIT_TIME_T
//#define BUILDING_DLL     コメントアウト

#include "postgres.h"
#include "funcapi.h"        //fmgr.hはインクルード済み        
#include "utils/builtins.h" //Postgres内部関数textout使用のため

//BUILDING_DLL使用不可
#ifdef BUILDING_DLL
#error Don't define BUILDING_DLL
#endif

//ライブラリリンク
#pragma comment(lib, "postgres.lib")        

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(test_func);

PGDLLIMPORT Datum test_func(PG_FUNCTION_ARGS)
{
    FuncCallContext *pFcctx;          
    MemoryContext oldmemoryctx;          
・
・

ビルドは出来ましたが警告はありますし、BUILDING_DLL を定義させないようにしたのでエクスポートができてなくて振り出しに戻りました。

<再定義>

現状のPG_MODULE_MAGICPG_FUNCTION_INFO_V1マクロには PGDLLIMPORTが含まれているのでこれは使えません。 ということでこれらを#undefして新しくつくります。

  1. 新しいスイッチ(VC_MODULE_BUILDING)を定義します。
  2. fmgr.hで定義されているPG_MODULE_MAGICPG_FUNCTION_INFO_V1 マクロを#undefします。
  3. pg_config_os.hで定義されいるスイッチを元に VC_MODULE_BUILDINGをスイッチとして PG_EXPORTを定義します。
  4. fmgr.hで定義されているPG_MODULE_MAGICPG_FUNCTION_INFO_V1マクロを参考に 新しいマクロを定義します。(PGDLLIMPORTPG_EXPORTに変える)
  5. test_funcをエクスポートできるようにPG_EXPORTに変える
//CFunc.cpp////////////////////////////////////////////////////////
#define _USE_32BIT_TIME_T
//#define BUILDING_DLL     コメントアウト

#include "postgres.h"
#include "funcapi.h"        //fmgr.hはインクルード済み        
#include "utils/builtins.h" //Postgres内部関数textout使用のため         

//BUILDING_DLL使用不可
#ifdef BUILDING_DLL
#error Don't define BUILDING_DLL 
#endif

//スイッチの定義                  (A)
#ifndef VC_MODULE_BUILDING
#define VC_MODULE_BUILDING
#endif /* VC_MODULE_BUILDING */

//既存のPG_MODELE_MAGIC, PG_FUNCTION_INFO_V1をキャンセル (B)
#undef PG_MODULE_MAGIC
#undef PG_FUNCTION_INFO_V1
 
//エクスポートスイッチ(VC_MODULE_BUILDINGによって__declspec(dllexport), (C)
                       __declspec(dllimport)を切り替える)
#if defined(_MSC_VER) || defined(__MINGW32__)
#if defined(VC_MODULE_BUILDING)
#define PG_EXPORT __declspec(dllexport)
#else
#define PG_EXPORT __declspec(dllimport)
#endif /* PG_MODULE_BUILDING */
#else
define PG_EXPORT
#endif /*_MSC_VER, __MINGW32__ */


//PG_MODULE_MAGICマクロ定義     (D)
#define PG_MODULE_MAGIC \
PG_EXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
const Pg_magic_struct * \
PG_MAGIC_FUNCTION_NAME(void) \
{ \
    static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
    return &Pg_magic_data; \
} \
extern int no_such_variable

//PG_FUNCTION_INFO_V1マクロ定義 (D)
#define PG_FUNCTION_INFO_V1(funcname) \
PG_EXPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void); \
const Pg_finfo_record * \
CppConcat(pg_finfo_,funcname) (void) \
{ \
    static const Pg_finfo_record my_finfo = { 1 }; \
    return &my_finfo; \
} \
extern int no_such_variable

//ライブラリリンク
#pragma comment(lib, "postgres.lib")

//テキスト取得マクロ(text型で取得した文字列をCで扱えるように変換) (E)
#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, \
                                                           PointerGetDatum(textp)))
#define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, \
                                                          CStringGetDatum(cstrp)))
 
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(test_func);

PG_EXPORT Datum test_func(PG_FUNCTION_ARGS) //(F)
{
    FuncCallContext *pFcctx;          
    MemoryContext oldmemoryctx;          
・
・
//        pSrc = DatumGetCString(
//                    DirectFunctionCall1(textout, (G)
//                                        PointerGetDatum(PG_GETARG_TEXT_P(i+1))));
          pSrc = GET_STR(PG_GETARG_TEXT_P(i+1));
・
・

これで一応形になりましたが、test_func内でPostgreSQLのtext型をC言語の char *に変換する処理はよく出てくるのでマクロにしてしまいます。 (逆の処理もついでにマクロにします。) (E)
これでtest_func内の(G)の処理をマクロで代用します。

・VC++ 2005開発用ヘッダファイル

このまま使ってもいいのですが、VC++で開発するときはいつも上記のような #undefと再定義を繰り返さないといけないので面倒です。そこで、VC++開発用の ヘッダファイルを作ってそこに必要な再定義を詰め込んじゃいます。
ツールバーのプロジェクト(P)→新しい項目の追加(W)からヘッダファイル(.h)を選択して ファイル名をvc.hとして追加ボタンを押してください。

vc.hヘッダファイルができたら、 CFunc.cppで記述したうち、VC++のとき必要なものを移していきます。

//vc.h///////////////////////////////////////////////////////////////////
/*
PostgreSQL C言語関数をVisual C++での開発に必要な修正    
*/
//postgres.h, funcapi.h fmgr.hのインクルード後に    
//このヘッダファイルをインクルードする事

#ifndef __VC_H__
#define __VC_H__

//BUILDING_DLL使用不可
#ifdef BUILDING_DLL
#error Don't define BUILDING_DLL
#endif

//スイッチの定義
#ifndef VC_MODULE_BUILDING
#define VC_MODULE_BUILDING
#endif /* VC_MODULE_BUILDING */

////既存のPG_MODELE_MAGIC, PG_FUNCTION_INFO_V1をキャンセル
#undef PG_MODULE_MAGIC
#undef PG_FUNCTION_INFO_V1

//エクスポートスイッチ(VC_MODULE_BUILDINGによって__declspec(dllexport), 
//__declspec(dllimport)を切り替える)
#if defined(_MSC_VER) || defined(__MINGW32__)
#if defined(VC_MODULE_BUILDING)
#define PG_EXPORT __declspec(dllexport)
#else
#define PG_EXPORT __declspec(dllimport)
#endif /* PG_MODULE_BUILDING */
#else
define PG_EXPORT
#endif /*_MSC_VER, __MINGW32__ */

//PG_MODULE_MAGICマクロ定義
#define PG_MODULE_MAGIC \    
PG_EXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
const Pg_magic_struct * \
PG_MAGIC_FUNCTION_NAME(void) \
{ \
  static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \
  return &Pg_magic_data; \
} \
extern int no_such_variable

//PG_FUNCTION_INFO_V1マクロ定義
#define PG_FUNCTION_INFO_V1(funcname) \
PG_EXPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void); \
const Pg_finfo_record * \
CppConcat(pg_finfo_,funcname) (void) \
{ \
  static const Pg_finfo_record my_finfo = { 1 }; \
  return &my_finfo; \
} \
extern int no_such_variable

//ライブラリリンク
#pragma comment(lib, "postgres.lib")

//Postgres内部関数textout使用のため
#ifndef BUILTINS_H
#include "utils/builtins.h" 
#endif /* BUILTINS_H */

//テキスト取得マクロ(text型で取得した文字列をCで扱えるように変換)
#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, \
                                                           PointerGetDatum(textp)))
#define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, \
                                                          CStringGetDatum(cstrp)))

#endif /* __VC_H__ */

これでVC++で開発するときはこのヘッダファイルを インクルードすればいいので楽になります。すっきりしたCFunc.cpp実装ファイルは以下のようになります。

//CFunc.cpp////////////////////////////////////////////////////////
#define _USE_32BIT_TIME_T

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

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(test_func);

PG_EXPORT 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);    

        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 = GET_STR(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);
    }
}

CFunc.dllはビルド時にライブラリファイルにコピーされているので、 前述したCREATE FUNCTIONを実行して、psqlやpgAdminから ここのSELECTコマンドを実行してみてください。

これでメデタシメデタシで終わりたいのですが、実はそうもいかないのです。 これはPostgreSQLが8.3系の話で、8.2系はさらにドツボにはまります。つづく・・・・・