Tech Note 05e: チュートリアル:共有ライブラリを作る

October 10, 2003

© NSB Corporation. All rights reserved.

[英語版]

このチュートリアルでは、C言語で書かれたPalm OS共有ライブラリをNSBasicプログラムに追加する方法を説明します。NSBasic3.2.0以降とCodeWarrior for Palm OS バージョン9を想定しています。

はじめに

共有ライブラリは、NSBasicプログラムの機能を拡張する1つの方法を提供します。共有ライブラリは小さい実行可能なコードで、NSBasicプログラムに組込むか、.prcファイルとして別に配付することができます。各共有ライブラリは、ユニークな名前を持った一連のプロシージャかファンクションを含んでいます。共有ライブラリのプロシージャは、NSBasicのサブルーチンと同じように呼ぶことができ、共有ライブラリのファンクションはNSBasicのファンクションと同様に考えられます。共有ライブラリはC言語で書かれ、直接ネイティブ マシンコードにコンパイルされているため、実行速度が速く、Palm OSの全ての機能にアクセスすることができます。

ステップ 1: デザイン

最初のステップでは共有ライブラリをデザインします。どのソフトウェアプロジェクトでも、最初のステップとしてデザインすることは重要ですが、共有ライブラリをデザインすることは特に重要なことです。最初のプロジェクトを作成してから、ファンクションやプロシージャの数を変更することは厄介な問題です。

私たちの最初の共有ライブラリは 、ベース変換パッケージでCnvと呼びます。正数を別のベース(2進や16進など)に変換するものと、その反対の変換を行う2つのファンクションを含みます。NSBasicでどうライブラリを使いたいかを想像して、デザインを開始します:

Dim s As String
Dim n As Integer
s = Cnv.DecToBase(37, 16)     's will be 37 in base 16
n = Cnv.BaseToDec("177", 8)   'n will be the value of 177 in octal
s = Cnv.DecToBase(42, 10)     'Works like Str for positive integers
入力が範囲外の場合、DecToBaseは空白の文字列を返し、BaseToDecは-1を返します。

今判断をする必要もないのですが、理解しておく必要があるので、もう1つデザイン的な決断があることを説明しておきます。共有ライブラリを別の.prcファイルとしてユーザーにダウンロードさせるか、NSBasicが作成する.prcファイルに組込むかです。別に配付するライブラリを"separate"、組込むライブラリを"embedded"として参照します。

ステップ 2: .infファイルの作成

NSBasicは、ライブラリをどう使うかを判断するために、コンパイル時に.inf拡張子を持つファイルを必要とします。C:\nsbasic\libに"Cnv.inf"というファイルを作り、次の内容をタイプします:

[General]
ExtensionName=Base-Conversion-Library
PrcName=Cnv.prc
Version=1.0 Alpha
InfVers=2.0
Manufacturer=myself

[GlobalMethods]
DecToBase = 1, func, 2, "convertedValue = DecToBase(in n as integer, in base as Integer) as String"
BaseToDec = 2, func, 2, "decValue = BaseToDec(in s as String, in base as Integer) as integer"

GlobalMechodsセクションの各ラインは、1つのファンクションかプロシージャを指定します。(もしラインが複数行に見えたとしても、入力の際は一行にタイプして下さい。)左から右へ、各プロシージャまたはファンクションは、名前、インデックス、タイプ、引数の数、ディスクリプターを持っています。

それぞれの名前はユニーク(他とは異なる)です。インデックスは、最初のファンクションかプロシージャが1になり、それ以降は間を空けずに、1づつ増えていきます。タイプは、ファンクションにはfuncを、プロシージャにはproc与えます。 この例では両方ともファンクションですが、プロシージャの使い方は後で説明します。次の数字はNSBasicから渡される引数の数です。ここでは両方とも2です。

ディスクリプターは、このバージョンのNSBasicに新しく追加されたものです。古いバージョンでは、ディスクリプター内はなんでも構いませんでした。新しいバージョンでは、ディスクリプターをタイプ情報として使用します。エラーがある場合、NSBasicは古いスタイルのライブラリと判断しますので、ディスクリプターを書く時は注意して下さい。新しい開発には、常に新しいスタイルを使って下さい。

DecToBaseファンクションのディスクリプターを見てみましょう:

convertedValue = DecToBase(in n as integer, in base as Integer) as String

"convertedValue = "はドキュメントの目的だけで、省略できます。次にファンクション名のDec2Baseがきて、括弧を開きます。(ファンクション名は、ラインの最初の名前と一致しなければなりません。括弧は引数がない場合でも必ず必要になります。)次に引数が並び、コンマで区切られます。最初の引数を見てみましょう:

in n as integer

これは3つの情報を指定しています:引数は"in"引数で、"n"という名前を持ち、integerタイプです。

"in"は、引数がファンクションに入っていく時だけに指定し、値として共有ライブラリに渡されます。"out"と"inout"は、引数が出てくる時か、入って出てくる時に使い、ポインターリファレンスとして渡されます。現在、"out"と"inout"には違いはありません。文字列とバリアントは、この決まりからは例外です。それらは常にポインターリファレンスとして渡され、"inout"引数として動作します。

次に2番目の引数を見てみましょう:

in base as Integer

ほぼ同様ですが、Integerが大文字で始まっています。これは大文字/小文字の違いは関係ないことを意味しています。

括弧を閉じた後、 as節があり、これは戻り値のタイプを指定しています。ここでは1つのStringを返します。

2番目のファンクションを見ると:

decValue = BaseToDec(in s as String, in base as Integer) as integer

最初の引数がStringタイプで"in"を定義しています。Stringは常に"inout"引数として扱われますが、"in"として定義すべきです。

共有ライブラリは、ほとんどのNSBasicタイプをサポートしています。共有ライブラリのコードを書く時、どのNSBasicタイプがどのCタイプのに対応しているかを知る必要があります。"out"と"inout"は、Cでは適切なタイプへのポインターとして参照されます(StringとVariantは除く。これらは既にポインター):

NSBasic type         .inf type                             C type
Integer		     Int32, Int4, long, UInt32, UInt4      long
Short                Int16, Int2, int, UInt16, UInt2       short
Float, Date, Time    double, date, time, Flt4, Flt32       double
Single               float, flt2, flt16                    float
Variant              Variant                               double *
String               String                                char *

この表でいくつか注意する部分があります。最初に、NSBasicタイプのFloatは、.infファイルとCではdoubleで、NSBasicタイプのSingleはfloatととして表されます。次に、variantは、歴史的な理由で、doubleへのポインターです。variantの唯一お薦めする使い方は、複数のファンクションコールの間にポインターを保持しておく使い方です。DateとTimeもdoubleとして表されます。

現在、配列全部を共有ライブラリに渡すことはできません。これは行うには、NSBasicの仕様を大きく変更する必要が あります。

ステップ 3: 共有ライブラリの作成

CodeWarrior 9 は共有ライブラリを作成するためのウィザードを提供しています。

CodeWarriorを起動し、前のセッションからのファイルが開いた場合は、全て閉じます。ファイル(File)メニューから新規(New...)を選択します。プロジェクトタブが選ばれていることを確認します。「Palm OS Shared Library Wizard」を選択し、Project名のフィールドに"Cnv"と入力して下さい。保存先を指定し、OKを押します。

4つのフィールドを持ったウィンドウが現れると思います。4つ全てのフィールドのデフォルト値を残してもそのまま動作します。商用の場合は、この時点でPalmからCreator IDを取得することをお薦めします。プロセスは無料で簡単です。「Visit Creator ID Website」を押し、指示に従うだけです。ここでは、テストが目的ですので、Creator IDとSTRTはそのままにします。「Next」ボタンを押し、現れる警告ダイアログも閉じます。

次にファンクション名を追加するウィンドウが見えると思います。.infファイルにある全てのファンクションとプロシージャを順番に入力します。「Add」を押し、"DecToBase"と入力し、再度「Add」を押し、"BaseToDec"と入力します。「Next」を押します。

プロジェクトに追加するためのライブラリのリストが表示されます。ほとんどの共有ライブラリは、他のライブラリを呼ぶ必要はありません。これらライブラリの多くは、実は共有ライブラリ内では動作しません。「Finish」ボタンを押して下さい。新しいプロジェクトが現れます。

ステップ 4: 共有ライブラリを書く

Sourceフォルダーに行くと、CnvImpl.cというファイルがあるはずですので、これを開きます。CodeWarriorは既に、多くのコードを作ってくれてあります。複数のファンクションコールの間に使うことができる、「グローバル」変数を処理するコードもそれらの1つです。CnvOpen、CnvClose、CnvSleep、CnvWakeファンクションを実行します。

私達の2つのファンクション用の定義も提供されています:CnvDecToBase、CnvBaseToDec (NSBasicにはファンクションとプロシージャがありますが、Cではどちらも「ファンクション」という言葉を使い、同じように実行されます。

CnvDecToBaseを見てみましょう。次のように定義されています:

Err CnvDecToBase(UInt16 refNum)

"Err"はこのファンクションがエラー値を戻すことを意味しています。これはNSBasicに戻される値とは違います。NSBasic用の全ての共有ライブラリのファンクションはerrNone(エラーがないことを意味する)を返すべきです。ファンクションは1つの引数(refNum)を持っています。これは常に同じです。これは共有ライブラリの特定のインスタンスのリファレンス番号です。(それらは共有されますので、いくつものアプリケーションがそれらを使うことができます。それぞれはユニークなリファレンス番号を持っています。)複数のファンクションコール間に値を保持しておく以外は、これを使う必要はありません。必要な場合は、CodeWarriorが提供するコードを参照して下さい。

このファンクションの実行を始めるには、.infファイル内のラインを見てみましょう:

convertedValue = DecToBase(in n as integer, in base as Integer) as String

タイプ変換の表を使って、必要な引数を引き出すことができます:

引数 1: Int32
引数 2: Int32
戻り値: char *

戻り値の定義は常に最後です。この情報を使って、Cコードの定義を以下のように変えます:

Err CnvDecToBase(UInt16 refNum, Int32 n, Int32 base, char *retVal)

さらにCnv.hに行き、Headersディレクトリ内の定義を以下のように変更します:

extern Err CnvDecToBase(UInt16 refNum, Int32 n, Int32 base, char *retVal)
	CNV_LIB_TRAP(sysLibTrapBase + 5);

同様に、CnvBaseToDec用に、.infラインを使って以下のように定義を変更します:

decValue = BaseToDec(in s as String, in base as Integer) as integer

Err CnvBaseToDec(UInt16 refNum, char *s, Int32 base, Int32 *retVal)

extern Err CnvBaseToDec(UInt16 refNum, char *s, Int32 base, Int32 *retVal)
	CNV_LIB_TRAP(sysLibTrapBase + 6);
戻り値がポインターとして定義されていることを注意して下さい。

残りは実際にファンクションを実装します。これは1つの実装例で、ウィザードで提供されたものに入れ替えて、CodeWarriorにタイプするかペーストできます:

Err CnvDecToBase(UInt16 refNum, Int32 n, Int32 base, char *retVal)
{
	#pragma unused(refNum)
	
	if (n < 0 || refNum < 2 || refNum > 32)
	{
	    /* Bad parameters */
	    StrCopy(retVal, "");
	    return errNone;
	}
	
	*retVal = '\0';
	do
	{
	    char *s;
	    char remainder;
	    
	    /* Open up a space for the next digit */
	    for (s = retVal; *s; ++s){}
	    for (++s; s > retVal; --s)
	    {
	        *s = *(s - 1);
	    }
	    
	    remainder = n % base;
	    
	    if (remainder < 10)
	    {
	        *retVal = '0' + remainder;
	    }
	    else
	    {
	        *retVal = 'A' + (remainder - 10);
	    }
	    
	    n /= base;
	} while (n > 0);
	
    return errNone;
}    


Err CnvBaseToDec(UInt16 refNum, char *s, Int32 base, Int32 *retVal)
{
	#pragma unused(refNum)
	char *runner;
	
	*retVal = 0;
	
	if (base < 2 || base > 36)
	{
	    /* Bad parameters */
	    *retVal = -1;
	    return errNone;
	}
	
	for (runner = s; *runner; ++runner)
	{
	    UInt16 digit;
	    
	    if (*runner >= '0' && *runner <= '9')
	    {
	        digit = *runner - '0';
	    }
	    else if (*runner >= 'A' && *runner <= 'Z')
	    {
	        digit = *runner - 'A' + 10;
	    }
	    else if (*runner >= 'a' && *runner <= 'z')
	    {
	        digit = *runner - 'a' + 10;
	    }
	    else
	    {
	        *retVal = -1;
	        return errNone;
	    }
	    
	    if (digit >= base)
	    {
	        *retVal = -1;
	        return errNone;
	    }
	    
	    *retVal = *retVal * 10 + digit;
	}
		
    return errNone;
}

ProjectメニューからMakeを選び、共有ライブラリをコンパイルします。タイピングエラーがなければ、コンパイルが完了し、Cnv.prcと名前を持つファイルができます。

NSBasicの共有ライブラリでは、バージョン番号を持つ文字列(この場合、 "1.0 Devel")からなる、ID 0と共に'tver'リソースを含めることをお薦めします。 しかし、CodeWarriorは共有ライブラリに追加リソースを足すことを妨げようとします。Palmのデベロッパーサイトにある、無料でダウンロードできるエディタを使って、ファイルが作成された後に、このリソースを追加して下さい。

ステップ 5: NSBasicプログラムで共有ライブラリを使う

NSBasicプログラムで、新しい共有ライブラリを使用し、テストしてみましょう。

最初に新しいプロジェクトを作成し、Startup Codeの中に、次のラインを入れます:

   LoadLibrary Cnv

ここでは単純な10進から16進への変換プログラムを作ります。2つのテキストフィールドを作ります。1つ目をDecと呼び、2つ目をHexと呼びます。ボタンを1つ作成し、そのボタンのサブルーチンに以下のラインを入れます:

Dim ins as String
Dim in as Integer
Dim outs as String

    ins = Dec.text
    in = Cnv.BaseToDec(ins, 10)
    outs = Cnv.DecToBase(in, 16)
    Hex.text = outs

実行してテストしてみます。.infファイルが正しくインストールされていると、コンパイルが始まります。しかし、走らせると、ライブラリが既にロードされているために、エラーメッセージが出ます。

始めに触れたように、ライブラリは"embedded"か"separate"です。Cnvをseparateライブラリとして動かすには、Palm OS EmulatorにあるInstall Application Databaseメニュー項目を使って、Cnv.prcをインストールします。

これで動作するはずです。 Decフィールドに10進の数値を入力し、ボタンを押します。Hexフィールドに対応する16進の数値が現れるはずです。

新しい機能によって、いくつもの共有ライブラリをアプリケーションに組込む(embedded)ことができるようになりました。これを試すには、まずPalm OS EmulatorからCnv共有ライブラリを削除します。NSBasicプロジェクトへ戻り、プロジェクトエクスプローラ内のResourcesフォルダーに、まだ何も入っていないことを確認します。

Resourceフォルダーを右クリックし、「リソースを追加」を選びます。Cnv.prcへとフォルダを操作し、このファイルを選んで開きます。新しいリソースが追加されます。リソースタイプを"libr"へ、名前を"Cnv"へ変更し、再コンパイルします。プロジェクトのサイズが少し大きくなったことに気が付くかもしれません。これは共有ライブラリが、プロジェクト内に存在するからです。これで共有ライブラリを別にインストールする必要無く、プログラムは正常に動作するはずです。