C/C++ による拡張機能の開発方法

注釈

現在、本章の説明だけでは拡張機能の開発は困難です。 Ring のソースコード (include, src, visualsrc, stdlib フォルダ)、および拡張機能のソースコード (android, extensions, libdepwin フォルダ) のコードリーディングを強く推奨します。

プログラミング言語 C / C++ で記述した関数を新たに追加することにより Ring 仮想計算機 (Ring VM) の機能を拡張できます。 Ring VM には C により記述された様々な関数が付属しており Ring の関数として呼び出せます。

新しい関数を記述してから Ring VM のリビルドすることで Ring 言語の機能を拡張できます。 また、共有ライブラリ (.dll / .so) ファイルを作成することで Ring VM をリビルドせずに機能の拡張ができます。

Ring のソースコードには Ring VM へ新しいモジュールを追加するための二本のファイルが付属しています。

ring_ext.h

ファイル ring_ext.h にはビルド処理の間にモジュールの包含、または除外を変更できる定数があります。

#ifndef ringext_h
#define ringext_h
/* 定数 */
#define RING_VM_LISTFUNCS       1
#define RING_VM_REFMETA         1
#define RING_VM_MATH            1
#define RING_VM_FILE            1
#define RING_VM_OS              1
#define RING_VM_MYSQL           1
#define RING_VM_ODBC            1
#define RING_VM_OPENSSL         1
#define RING_VM_CURL            1
#define RING_VM_DLL             1
#endif

ring_ext.c

モジュールのスタートアップ関数を呼び出す前に、ファイル ring_ext.c は ring_ext.h で定義されている定数を確認します。

モジュールは Ring VM へモジュール関数を登録する関数があります。

#include "ring.h"

void ring_vm_extension ( RingState *pRingState )
{
        /* リフレクションとメタプログラミング */
        #if RING_VM_REFMETA
                ring_vm_refmeta_loadfunctions(pRingState);
        #endif
        /* リスト関数 */
        #if RING_VM_LISTFUNCS
                ring_vm_listfuncs_loadfunctions(pRingState);
        #endif
        /* 算術 */
        #if RING_VM_MATH
                ring_vm_math_loadfunctions(pRingState);
        #endif
        /* ファイル */
        #if RING_VM_FILE
                ring_vm_file_loadfunctions(pRingState);
        #endif
        /* OS */
        #if RING_VM_OS
                ring_vm_os_loadfunctions(pRingState);
        #endif
        /* MySQL */
        #if RING_VM_MYSQL
                ring_vm_mysql_loadfunctions(pRingState);
        #endif
        /* ODBC */
        #if RING_VM_ODBC
                ring_vm_odbc_loadfunctions(pRingState);
        #endif
        /* OPENSSL */
        #if RING_VM_OPENSSL
                ring_vm_openssl_loadfunctions(pRingState);
        #endif
        /* CURL */
        #if RING_VM_CURL
                ring_vm_curl_loadfunctions(pRingState);
        #endif
        /* DLL */
        #if RING_VM_DLL
                ring_vm_dll_loadfunctions(pRingState);
        #endif
}

モジュールの組織化

モジュールごとに Ring ヘッダファイル (ring.h) をインクルードすることから始まります。 このファイルには Ring VM の拡張機能で用いる Ring API があります。

モジュールには Ring VM へモジュール関数を登録するための関数が付属しています。 ring_vm_funcregister() 関数で登録を完了します。

ring_vm_funcregister() 関数は以下の仮関数を扱います。 一番目の仮引数は Ring プログラムが関数を呼び出すために使用される関数名です。 二番目の仮引数は C プログラム側の関数ポインタです。

この用例では、モジュール関数の登録用コードが ring_vmmath.c モジュールにあります。

#include "ring.h"

void ring_vm_math_loadfunctions ( RingState *pRingState )
{
        ring_vm_funcregister("sin",ring_vm_math_sin);
        ring_vm_funcregister("cos",ring_vm_math_cos);
        ring_vm_funcregister("tan",ring_vm_math_tan);
        ring_vm_funcregister("asin",ring_vm_math_asin);
        ring_vm_funcregister("acos",ring_vm_math_acos);
        ring_vm_funcregister("atan",ring_vm_math_atan);
        ring_vm_funcregister("atan2",ring_vm_math_atan2);
        ring_vm_funcregister("sinh",ring_vm_math_sinh);
        ring_vm_funcregister("cosh",ring_vm_math_cosh);
        ring_vm_funcregister("tanh",ring_vm_math_tanh);
        ring_vm_funcregister("exp",ring_vm_math_exp);
        ring_vm_funcregister("log",ring_vm_math_log);
        ring_vm_funcregister("log10",ring_vm_math_log10);
        ring_vm_funcregister("ceil",ring_vm_math_ceil);
        ring_vm_funcregister("floor",ring_vm_math_floor);
        ring_vm_funcregister("fabs",ring_vm_math_fabs);
        ring_vm_funcregister("pow",ring_vm_math_pow);
        ring_vm_funcregister("sqrt",ring_vm_math_sqrt);
        ring_vm_funcregister("unsigned",ring_vm_math_unsigned);
        ring_vm_funcregister("decimals",ring_vm_math_decimals);
        ring_vm_funcregister("murmur3hash",ring_vm_math_murmur3hash);
}

ちなみに

ring_vm_math_loadfunctions() 関数は ring_vm_extension() 関数により、 呼び出されることを覚えておいてください (ring_ext.c ファイルで)。

関数の構造

このような手順がモジュール関数ごとにあります。

1 - 仮引数の個数を検査

2 - 仮引数の型を検査

3 - 仮引数の値を取得

4 - コードの実行 / 関数の呼び出し

5 - 返値を返す

この構造は関数と非常に似ています (入力~処理~出力)。 ここでは Ring API の 1, 2, 3, および 5 の手順を使用しています。

仮引数の個数を検査するには

RING_API_PARACOUNT マクロで仮引数の個数を検査します。

RING_API_PARACOUNT は == または != 演算子により数値で比較します。

用例:

if ( RING_API_PARACOUNT != 1 ) {
        /* コード */
}

用例:

if ( RING_API_PARACOUNT == 1 ) {
        /* コード */
}

エラーメッセージの表示

RING_API_ERROR() 関数はエラーメッセージを表示します。

この関数はエラー、およびプログラム実行時に表示します。

注釈

この関数の動作は Ring コードで Try/Catch/Done ステートメントにより変更できますので C コードでは、この関数の後に retrun を記述してください。

文法:

RING_API_ERROR(const char *cErrorMsg);

Ring API には定義済みのエラーメッセージがあり、このように使うことができます。

#define RING_API_MISS1PARA "Bad parameters count, the function expect one parameter"
#define RING_API_MISS2PARA "Bad parameters count, the function expect two parameters"
#define RING_API_MISS3PARA "Bad parameters count, the function expect three parameters"
#define RING_API_MISS4PARA "Bad parameters count, the function expect four parameters"
#define RING_API_BADPARATYPE    "Bad parameter type!"
#define RING_API_BADPARACOUNT   "Bad parameters count!"
#define RING_API_BADPARARANGE   "Bad parameters value, error in range!"
#define RING_API_NOTPOINTER     "Error in parameter, not pointer!"
#define RING_API_NULLPOINTER    "Error in parameter, NULL pointer!"
#define RING_API_EMPTYLIST      "Bad parameter, empty list!"

仮引数の型を検査するには

この関数は仮引数の型を検査します。

int RING_API_ISNUMBER(int nParameterNumber);
int RING_API_ISSTRING(int nParameterNumber);
int RING_API_ISLIST(int nParameterNumber);
int RING_API_ISCPOINTER(int nParameterNumber);
int RING_API_ISPOINTER(int nParameterNumber);  // リストまたは C ポインタ

これらの関数の実行結果は 1 (True) または 0 (False) です。

仮引数の値を取得するには

この関数は仮引数の値を取得します。

double RING_API_GETNUMBER(int nParameterNumber);
const char *RING_API_GETSTRING(int nParameterNumber);
int RING_API_GETSTRINGSIZE(int nParameterNumber);
List *RING_API_GETLIST(int nParameterNumber);
void *RING_API_GETCPOINTER(int nParameterNumber, const char *cPoinerType);
int RING_API_GETPOINTERTYPE(int nParameterNumber);

返値

この関数は関数の返値を取得します。

RING_API_RETNUMBER(double nValue);
RING_API_RETSTRING(const char *cString);
RING_API_RETSTRING2(const char *cString,int nStringSize);
RING_API_RETLIST(List *pList);
RING_API_RETCPOINTER(void *pValue,const char *cPointerType);
RING_API_RETMANAGEDCPOINTER(void *pValue,const char *cPointerType,
                                void (* pFreeFunc)(void *,void *))

関数プロトタイプ

Ring VM の拡張機能用の新しい関数を定義するには、このプロトタイプを使用します。

void my_function_name( void *pPointer );

または RING_FUNC() マクロを使えます。

RING_FUNC(my_function_name);

Sin() 関数の実装

このコードは Ring API と C 関数 sin() による Sin() 関数の実装です。

void ring_vm_math_sin ( void *pPointer )
{
        if ( RING_API_PARACOUNT != 1 ) {
                RING_API_ERROR(RING_API_MISS1PARA);
                return ;
        }
        if ( RING_API_ISNUMBER(1) ) {
                RING_API_RETNUMBER(sin(RING_API_GETNUMBER(1)));
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

Fopen() および Fclose() の実装

このコードは Ring API と C 関数 fopen() による fopen() 関数の実装です。

この関数は以下の仮引数を扱います。第一仮引数はファイル名を文字列で指定します。 第二仮引数はモードを文字列で指定します。

ファイル ring_vmfile.h はポインタ型などの定数として使用するものがあります。

#define RING_VM_POINTER_FILE    "file"
#define RING_VM_POINTER_FILEPOS "filepos"

ring_vmfile.c における関数の実装です。

void ring_vm_file_fopen ( void *pPointer )
{
        FILE *fp  ;
        if ( RING_API_PARACOUNT != 2 ) {
                RING_API_ERROR(RING_API_MISS2PARA);
                return ;
        }
        if ( RING_API_ISSTRING(1) && RING_API_ISSTRING(2) ) {
                fp = fopen(RING_API_GETSTRING(1),RING_API_GETSTRING(2));
                RING_API_RETCPOINTER(fp,RING_VM_POINTER_FILE);
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

このコードは fclose() 関数の実装です。

void ring_vm_file_fclose ( void *pPointer )
{
        FILE *fp  ;
        if ( RING_API_PARACOUNT != 1 ) {
                RING_API_ERROR(RING_API_MISS1PARA);
                return ;
        }
        if ( RING_API_ISCPOINTER(1) ) {
                fp = (FILE *) RING_API_GETCPOINTER(1,RING_VM_POINTER_FILE) ;
                if ( fp != NULL ) {
                        RING_API_RETNUMBER(fclose(fp));
                        RING_API_SETNULLPOINTER(1);
                }
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

fopen() および fclose() から次の実装について学びました

1 - RING_API_RETCPOINTER() 関数で C ポインタを返す方法

2 - RING_API_ISCPOINTER() 関数による仮引数がポインタであるときの検査方法

3 - RING_API_GETCPOINTER() 関数による C ポインタ値の取得方法

4 - RING_API_SETNULLPOINTER() 関数で C ポインタ変数 (Ring VM では) へ NULL を設定する方法

Ring API - リスト関数

リストの新規作成とリスト項目操作用の Ring API リスト関数です。

List * ring_list_new ( int nSize ) ;
void ring_list_newitem ( List *pList ) ;
Item * ring_list_getitem ( List *pList,int index ) ;
List * ring_list_delete ( List *pList ) ;
void ring_list_deleteitem ( List *pList,int index ) ;
void ring_list_print ( List *pList ) ;
int ring_list_gettype ( List *pList, int index ) ;
void ring_list_setint ( List *pList, int index ,int number ) ;
void ring_list_addint ( List *pList,int x ) ;
void ring_list_setpointer ( List *pList, int index ,void *pValue ) ;
void ring_list_addpointer ( List *pList,void *pValue ) ;
void ring_list_setfuncpointer ( List *pList, int index ,void (*pFunc)(void *) ) ;
void ring_list_addfuncpointer ( List *pList,void (*pFunc)(void *) ) ;
int ring_list_isfuncpointer ( List *pList, int index ) ;
void ring_list_setdouble ( List *pList, int index ,double number ) ;
void ring_list_adddouble ( List *pList,double x ) ;
void ring_list_setstring ( List *pList, int index ,const char *str ) ;
void ring_list_setstring2 ( List *pList, int index ,const char *str,int nStrSize ) ;
void ring_list_addstring ( List *pList,const char *str ) ;
void ring_list_addstring2 ( List *pList,const char *str,int nStrSize ) ;
List * ring_list_newlist ( List *pList ) ;
List * ring_list_getlist ( List *pList, int index ) ;
void ring_list_setlist ( List *pList, int index ) ;
void ring_list_setactiveitem ( List *pList, Items *pItems, int index ) ;
void ring_list_copy ( List *pNewList, List *pList ) ;
int ring_list_isnumber ( List *pList, int index ) ;
int ring_list_isstring ( List *pList, int index ) ;
int ring_list_islist ( List *pList, int index ) ;
int ring_list_ispointer ( List *pList, int index ) ;
void ring_list_deleteallitems ( List *pList ) ;
void ring_list_insertitem ( List *pList,int x ) ;
void ring_list_insertint ( List *pList,int nPos,int x ) ;
void ring_list_insertdouble ( List *pList,int nPos,double x ) ;
void ring_list_insertpointer ( List *pList,int nPos,void *pValue ) ;
void ring_list_insertstring ( List *pList,int nPos,const char *str ) ;
void ring_list_insertstring2 ( List *pList,int nPos,const char *str,int nStrSize ) ;
void ring_list_insertfuncpointer ( List *pList,int nPos,void (*pFunc)(void *) ) ;
List * ring_list_insertlist ( List *pList,int nPos ) ;
int ring_list_isiteminsidelist ( List *pList,Item *pItem ) ;
int ring_list_findstring ( List *pList,const char *str,int nColumn ) ;
int ring_list_finddouble ( List *pList,double nNum1,int nColumn ) ;
void ring_list_sortnum ( List *pList,int left,int right,int nColumn ) ;
void ring_list_sortstr ( List *pList,int left,int right,int nColumn ) ;
int ring_list_binarysearchnum ( List *pList,double nNum1,int nColumn ) ;
int ring_list_binarysearchstr ( List *pList,const char *cFind,int nColumn ) ;
void ring_list_swap ( List *pList,int x,int y ) ;
double ring_list_getdoublecolumn ( List *pList,int nIndex,int nColumn ) ;
char * ring_list_getstringcolumn ( List *pList,int nIndex,int nColumn ) ;
void ring_list_genarray ( List *pList ) ;
void ring_list_deletearray ( List *pList ) ;
void ring_list_genhashtable ( List *pList ) ;
void ring_list_genhashtable2 ( List *pList ) ;
void ring_list_refcopy ( List *pNewList, List *pList ) ;
void ring_list_clear ( List *pList ) ;
/* マクロ */
ring_list_isdouble(pList,index)
ring_list_isint(pList,index)
ring_list_deletelastitem(x)
ring_list_gethashtable(x)
ring_list_getint(pList,index)
ring_list_getpointer(pList,index)
ring_list_getfuncpointer(pList,index)
ring_list_callfuncpointer(pList,index,x)
ring_list_getdouble(pList,index)
ring_list_getstring(pList,index)
ring_list_getstringobject(pList,index)
ring_list_getstringsize(pList,index)
ring_list_getsize(x) (x->nSize)

Ring API - 文字列関数

文字列の新規作成と文字列内容操作用の Ring API 文字列関数です。

String * ring_string_new ( const char *str ) ;
String * ring_string_new2 ( const char *str,int nStrSize ) ;
String * ring_string_delete ( String *pString ) ;
int ring_string_size ( String *pString ) ;
void ring_string_set ( String *pString,const char *str ) ;
void ring_string_set2 ( String *pString,const char *str,int nStrSize ) ;
void ring_string_add ( String *pString,const char *str ) ;
void ring_string_add2 ( String *pString,const char *str,int nStrSize ) ;
void ring_string_print ( String *pString ) ;
void ring_string_setfromint ( String *pString,int x ) ;
char * ring_string_lower ( char *cStr ) ;
char * ring_string_upper ( char *cStr ) ;
char * ring_string_lower2 ( char *cStr,int nStrSize ) ;
char * ring_string_upper2 ( char *cStr,int nStrSize ) ;
char * ring_string_find ( char *cStr1,char *cStr2 ) ;
char * ring_string_find2 ( char *cStr1,int nStrSize1,char *cStr2,int nStrSize2 ) ;
/* マクロ */
ring_string_tolower(x)
ring_string_toupper(x)
ring_string_get(x)

MySQL_Columns() 関数の実装

このコードは MySQL_Columns() 関数の実装です。

この関数はカラム情報を返します。

void ring_vm_mysql_columns ( void *pPointer )
{
        MYSQL *con  ;
        MYSQL_RES *result  ;
        int nColumns,x  ;
        MYSQL_ROW row  ;
        MYSQL_FIELD *field  ;
        List *pList, *pList2  ;
        if ( RING_API_PARACOUNT != 1 ) {
                RING_API_ERROR(RING_API_MISS1PARA);
                return ;
        }
        if ( RING_API_ISCPOINTER(1) ) {
                con = (MYSQL *) RING_API_GETCPOINTER(1,RING_VM_POINTER_MYSQL) ;
                if ( con == NULL ) {
                        return ;
                }
                result = mysql_store_result(con);
                if ( result == NULL ) {
                        RING_API_RETNUMBER(0);
                        return ;
                }
                pList = RING_API_NEWLIST ;
                nColumns = mysql_num_fields(result);
                if ( row = mysql_fetch_row(result) ) {
                        while ( field = mysql_fetch_field(result) ) {
                                pList2 = ring_list_newlist(pList);
                                ring_list_addstring(pList2,field->name);
                                ring_list_adddouble(pList2,field->length);
                                ring_list_adddouble(pList2,field->type);
                                ring_list_adddouble(pList2,field->flags);
                        }
                }
                mysql_free_result(result);
                RING_API_RETLIST(pList);
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

List はリスト型であり、以前の関数でリスト型のポインタ *pList, *pList2; を用いて宣言されています。

注釈

この関数は新しいリストの作成で RING_API_NEWLIST を使いますが ring_list_new() の代わりに一時作業用のリストを作成します。関数のスコープはメモリと関連付けられています。 このように関数からリストを返せます。また Ring コードにより保存された格納済みの変数ならば、 リストは削除しません。それ以外ならば Ring VM により自動削除されます。

リストは部分リストを有することができ、部分リストは ring_list_newlist() 関数で作成します。

ring_list_addstring() 関数は部分リストへ文字列の項目を追加します。

ring_list_adddouble() 関数は部分リストへ数値の項目を追加します。

注釈

Ring VM 拡張機能の関数から返されたリストにある数値の項目は倍精度実数型 (double) であるため ring_list_adddouble() 関数でリストへ追加してください。

RING_API_RETLIST() 関数は拡張機能の関数からリストを返します。

動的 / 共有ライブラリ (DLL/So/Dylib) および LoadLib() 関数

新しい関数を C/C++ と Ring API で記述後に Ring VM のリビルドをするのではなく、 DLL /So / Dylib ファイルの作成を行い、このファイルを実行時に LoadLib() 関数へ指定することで動的に関数を使えます。

C による動的ライブラリの用例

#include "ring.h"

RING_FUNC(ring_ringlib_dlfunc)
{
        printf("Message from dlfunc");
}

RING_API void ringlib_init(RingState *pRingState)
{
        ring_vm_funcregister("dlfunc",ring_ringlib_dlfunc);
}

考えかたとしては ringlib_init() 関数を作成することであり、この関数は生成された DLL ファイルを LoadLib() 関数で読み込んで使用する時に Ring VM から呼び出されます。

ringlib_init() 関数内でモジュール関数の登録、またはモジュール関数を全て登録処理するための関数を呼び出せます。

この Ring コードは実行時に DLL ライブラリの用法です。

See "Dynamic DLL" + NL
LoadLib("ringlib.dll")
dlfunc()

実行結果:

Dynamic DLL
Message from dlfunc

RING_API_RETMANAGEDCPOINTER() の用法

RING_API_RETMANAGEDCPOINTER() は C/C++ 言語で記述された Ring 拡張機能でマネージドポインタを Ring へ返します。このポインタは参照カウンタを使用している Ring VM により制御可能です。

これは RingQt の QPixMap オブジェクトなどのアンマネージドのリソース解放用コードを書く必要性を回避するには重要です。

また、コード生成器は必要に応じて拡張機能を RING_API_RETMANAGEDCPOINTER() に対応したものへ自動更新します。

文法:

RING_API_RETMANAGEDCPOINTER(void *pValue,const char *cPointerType,
                                void (* pFreeFunc)(void *,void *))

用例:

この用例は ring_qt.cpp の QPixMap クラスにある Scaled() メソッドからの引用です。

RING_FUNC(ring_QPixmap_scaled)
{
        QPixmap *pObject ;
        if ( RING_API_PARACOUNT != 5 ) {
                RING_API_ERROR(RING_API_BADPARACOUNT);
                return ;
        }
        RING_API_IGNORECPOINTERTYPE ;
        if ( ! RING_API_ISCPOINTER(1) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        pObject = (QPixmap *) RING_API_GETCPOINTER(1,"QPixmap");
        if ( ! RING_API_ISNUMBER(2) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        if ( ! RING_API_ISNUMBER(3) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        if ( ! RING_API_ISNUMBER(4) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        if ( ! RING_API_ISNUMBER(5) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        {
                QPixmap *pValue ;
                pValue = new QPixmap() ;
                *pValue = pObject->scaled( (int ) RING_API_GETNUMBER(2),
                         (int ) RING_API_GETNUMBER(3),
                         (Qt::AspectRatioMode )  (int) RING_API_GETNUMBER(4),
                         (Qt::TransformationMode )  (int) RING_API_GETNUMBER(5));
                RING_API_RETMANAGEDCPOINTER(pValue,"QPixmap",ring_QPixmap_freefunc);
        }
}

この関数は二種類の仮引数 (Ring のステートと確保されたメモリのポインタ) の指定によりメモリを解放します。

用例:

void ring_QPixmap_freefunc(void *pState,void *pPointer)
{
        QPixmap *pObject ;
        pObject = (QPixmap *) pPointer;
        delete pObject ;
}