PascalやC,C++などを利用してDLLを自作することも可能。
その場合の要点は,公開する関数にstdcall(C++では,__stdcall)を指定すること。
Delphiと互換性を持つPascal開発環境FPC+Lazarusをダウンロードしてインストールする。
Lazarus Windows 32bit
Windows版十進BASICで使うDLLを作るのが目的であれば,OSが64ビット版Windowsの場合でも32ビットWindows版を選ぶ。
<Memo>
PascalとBASICの類似点:Pascalは,大文字と小文字を同一視する(文字列定数を除く)。
PascalとBASICが異なる点:Pascalでは改行は空白と同じ意味しか持たない。代わりに文と文の区切りとしてセミコロンが書かれる。
参照 Pascal言語の概要
Object Pascal Tutorial /ja(日本語訳)
ixam Pascal入門
Delphiのすすめ
Mr.Xray Delphi Library
Object Pascal入門『ソースは語る』
FPC Pascalでは,program 〜 end.の代わりに,library 〜 end. でDLLを作れる。
BASICから呼び出す関数(手続きも可)にはstdcallを指示し,それらの名前をexportsに書く。
BASICから使うときには,関数(手続き)の名前は,大文字と小文字の違いが識別される。
exportsに書いた名前と関数(手続き)の頭書きに書いた名前の文字の大小が違っていてもDLLの翻訳はできるので注意が必要。BASICから使うときの名前はexport文で書かれた名前。
関数の場合,結果の型は,実数型,文字列型以外で,内部表現が8ビット,16ビット,または,32ビットであるもの。ただし,8ビット,または,16ビットのときは,BASICの側で上位ビットを切り捨てる処理が必要。
引数は原則として値引数にする。引数にできるのは,実数型,文字列型以外で,内部表現が8ビット,16ビット,または32ビットであるもの。ただし,ポインタを引数にする代わりに変数引数にすることもできる。
文字列をPascalに渡すとき,Pascalの関数(手続き)ではPCharで受ける。
Pointerを扱う場合,BASICにはポインタ型がないので,数値変数を用いて値を保持する。
DLLの内部でのみ利用する関数・手続きには上述の制限はない。
例 2数の和,差を求める関数
1.次の内容をメモ帳で作成し,「ファイル」メニューから「名前を付けて保存」を選び,下段の「ファイルの種類」を「すべてのファイル(*.*)」に変えて適宜のフォルダにSample.pasというファイル名で保存する。
library Sample; function add(a,b:Longint): Longint; stdcall; begin add:=a+b end; function sub(a,b:Longint): Longint; stdcall; begin sub:=a-b end; exports add, sub; end.
Note. Longintは,32ビット符号付き整数型.
2.Lazarusの「プロジェクト」メニューの「ファイルから新規プロジェクト」でSample.pasを指定し,
「新規にプロジェクトを作成」ダイアログで,「ライブラリ」を選び,
「実行」メニューの「コンパイル」を実行するとSample.dllができる。
3.BASICのプログラムでは,次のようにして利用する。
(プログラムをDLLがあるのと同じフォルダに保存してから実行してください。)
100 FUNCTION ADD(a,b) 110 ASSIGN "Sample.dll","add" 120 END FUNCTION 130 140 FUNCTION SUB(a,b) 150 ASSIGN "Sample.dll","sub" 160 END FUNCTION 170 180 PRINT ADD(5,-4),SUB(4,7) 190 END
補足
引数はプログラムに書かれた順に対応付けられる。仮引数名とは無関係。だから,双方のプログラムで仮引数名が異なっていてもよい。
Pascalの関数名とBASICのASSIGN文に書く関数名は,大文字と小文字の違いまで含めて一致させる。
Visual BASICのAND,OR,NOT,XOR,IMP,EQVに相当するビット演算。
AND, OR, NOTはPascalの予約語なので,Pascalプログラムでは,関数名をANDop,ORop,NOTopなどとする。
関数定義におけるresultはDelphi独自の拡張なので,FPCで使うために{$MODE Delphi}を書いて構文解釈をDelphiモードにする。
library bitOp; {$MODE Delphi} function ANDop(a,b:Longint):Longint;stdcall; begin result:=a and b end; function ORop(a,b:Longint):Longint;stdcall; begin result:=a or b end; function NOTop(a:Longint):Longint;stdcall; begin result:=not a end; function XORop(a,b:Longint):Longint;stdcall; begin result:=a xor b end; function IMPop(a,b:Longint):Longint;stdcall; begin result:=not a or b end; function EQVop(a,b:Longint):Longint;stdcall; begin result:=not (a xor b) end; exports ANDop, ORop, NOTop, XORop, IMPop, EQVop; end.
BASICでの使い方
NOTはFull BASICの予約語なので,関数名をBitInvにした。
DECLARE EXTERNAL FUNCTION AND,OR,XOR,IMP,EQV,BitInv LET a=BVAL("1010",2) LET b=BVAL("11",2) PRINT AND(a,b),OR(a,b),XOR(a,b),IMP(a,b),EQV(a,b) PRINT BitInv(a),BitInv(b) END EXTERNAL FUNCTION AND(a,b) ASSIGN "BitOp.dll","ANDop" END FUNCTION EXTERNAL FUNCTION OR(a,b) ASSIGN "BitOp.dll","ORop" END FUNCTION EXTERNAL FUNCTION XOR(a,b) ASSIGN "BitOp.dll","XORop" END FUNCTION EXTERNAL FUNCTION IMP(a,b) ASSIGN "BitOp.dll","IMPop" END FUNCTION EXTERNAL FUNCTION EQV(a,b) ASSIGN "BitOp.dll","EQVop" END FUNCTION EXTERNAL FUNCTION BitInv(a) ASSIGN "BitOp.dll","NOTop" END FUNCTION
DLLに独自のメモリ管理を追加し,メモリの解放を怠ったとしてもDLLを利用するプログラムの実行が終わればアドレス空間が解放されるようにしておく。
以下の内容をHEAPMEM.PASとして保存
unit HeapMem; interface uses Windows; var HHeap:THandle; implementation initialization HHeap:=HeapCreate(0,0,0); finalization HeapDestroy(HHeap); end.次に,次の内容をBitArray.pasとして保存してLazarusからコンパイルする。
library BitArray; {$MODE DELPHI} uses Windows, HeapMem in 'HEAPMEM.PAS'; function MemSize(s:Longint):Longint; begin result:=(s shr 5) shl 2; if (s and 31) <>0 then inc(result,4) end; const HEAP_ZERO_MEMORY = $00000008; function GetArray(s:Longint):pointer;stdcall; begin Result:=HeapAlloc(HHeap,HEAP_ZERO_MEMORY,MemSize(s)); end; procedure FreeArray(p:pointer); stdcall; begin HeapFree(HHeap,0,p) end; function Test(p:pointer; i:Longint):Longint;stdcall; begin asm mov eax,i mov edx,p bt [edx],eax mov eax,0 rcl eax,1 mov result,eax end; end; procedure SetBit(p:pointer; i:Longint);stdcall; begin asm mov eax,i mov edx,p bts [edx],eax end; end; procedure ResetBit(p:pointer; i:Longint);stdcall; begin asm mov eax,i mov edx,p btr [edx],eax end; end; exports GetArray,FreeArray,Test,SetBit,ResetBit ; end.
BASICのプログラムでは次のように利用する。
REM エラトステネスの篩 DECLARE EXTERNAL FUNCTION GetArray, Test DECLARE EXTERNAL SUB FreeArray, setbit LET Nmax=10000 LET p=GetArray(Nmax) IF p<>0 THEN FOR i=2 TO Nmax-1 IF Test(p,i)=0 THEN PRINT i FOR j=i^2 TO Nmax-1 STEP i CALL SetBit(p,j) NEXT j END IF NEXT i CALL FreeArray(p) END IF END EXTERNAL FUNCTION GetArray(s) ! (s+1)ビットのメモリを確保し,ゼロで埋める。 ! 結果は,メモリのアドレス。0のときは失敗。 FUNCTION GetArray_sub(s) ASSIGN "BitArray.DLL","GetArray" END FUNCTION IF 0<=s AND s<2^32 THEN LET GetArray=getArray_sub(s) ELSE LET GetArray=0 END if END FUNCTION EXTERNAL SUB FreeArray(p) ! メモリを返却する。pはGetArrayで得た値 ASSIGN "BitArray.DLL","FreeArray" END SUB EXTERNAL FUNCTION Test(p,i) ! i番目のビットを取得する。pはGetArrayで得た値 ASSIGN "BitArray.DLL", "Test" END FUNCTION EXTERNAL SUB SetBit(p,i) ! i番目のビットを1にする。pはGetArrayで得た値 ASSIGN "BitArray.DLL", "SetBit" END SUB EXTERNAL SUB ResetBit(p,i) ! i番目のビットを0にする。pはGetArrayで得た値 ASSIGN "BitArray.DLL", "ResetBit" END SUB
Test,SetBit,ResetBitで指定できるiの値は,GetArrayに指定した数をsとして,0〜sである。
また,実際にGetArrayに指定できるsの値の上限は,Windows XPでの実験結果では,1983381248で,これは2^30を超えるが,2^31よりは小さい
。
<注意>Test,SetBit,resetBitは範囲外の数を指定したときの安全策を講じていない。
次のDLLは,文字列変数を用いずに,メモリーを直接操作する関数・手続きを用意する。
library memory; uses Windows, HeapMem in 'HEAPMEM.PAS'; function GetMem(s:Longint):pointer;stdcall; begin Result:=HeapAlloc(HHeap,0,s); end; procedure FreeMem(p:pointer); stdcall; begin HeapFree(HHeap,0,p) end; type PByte=^byte; PWord=^word; PLongint=^Longint; function Peek(p:PByte):Longint;stdcall; begin result:=p^ end; function Peek2(p:PWord):Longint;stdcall; begin result:=p^ end; function Peek4(p:PLongint):Longint;stdcall; begin result:=p^ end; procedure Poke(p:PByte; i:byte);stdcall; begin p^:=i end; procedure Poke2(p:PWord; i:word);stdcall; begin p^:=i end; procedure Poke4(p:PLongint; i:Longint);stdcall; begin p^:=i end; procedure MoveM(p,q:Pointer; n:Longint);stdcall; begin move(p^,q^,n) end; exports GetMem,FreeMem,Peek,Peek2,Peek4,Poke,Poke2,Poke4,MoveM ; end.
使い方はこちら。
これを土台にして修正・拡張していけば,好みの出力ウィンドウが作れる。
(1) Lazarusの「ファイル」メニューから「新規フォーム」を選ぶ。
(2) フォームにメモ・コントロール(TMemo)を貼り付ける。
次の(3),(4)は飛ばして(5)に進んでもよい。
(3) オブジェクト・インスペクタを利用して,memo1のAlignプロパティをalClientに変更する。
(4) 同じく,memo1のlinesプロパティをクリックして,「文字列リストの設定」を開いて,入力されている文字列“memo1”を消す。
(5) 適当なフォルダにwindow1.pasという名前で保存する。
(6) メモ帳で次の内容を作成し,Sample1.pasという名前でwindow1.pasと同じフォルダに保存する。
library Sample1; uses Forms, Interfaces, window1; procedure Show; stdcall; begin Form1.Show end; procedure Hide; stdcall; begin Form1.Hide end; procedure TextOut(p:PChar);stdcall; begin Form1.Memo1.Lines.Append(p) end; procedure Erase;stdcall; begin Form1.Memo1.Lines.SetText('') end; procedure Run;stdcall; begin Application.Run; end; exports Show,Hide,TextOut,Erase,Run; initialization Application.Initialize; Application.CreateForm(TForm1, Form1); finalization Application.terminate; end.
(7)Lazarusの「プロジェクト」メニューの「ファイルから新規プロジェクト」でSample1.pasを指定し,「新規にプロジェクトを作成」ダイアログで「アプリケーション」を選び,「実行」メニューで「コンパイル」を実行すると,Sample1.dllが生成される。
(8)BASICプログラムでは,次のようにして利用する。
100 SUB Show 110 ASSIGN "Sample1.dll", "Show" 120 END SUB 130 SUB Hide 140 ASSIGN "Sample1.dll", "Hide" 150 END SUB 160 SUB TextOut(s$) 170 ASSIGN "Sample1.dll", "TextOut" 180 END SUB 190 SUB Erase 200 ASSIGN "Sample1.dll", "Erase" 210 END SUB 220 SUB Run 230 ASSIGN "Sample1.dll", "Run" 240 END SUB 250 CALL Show 260 CALL TextOut("Hello") 270 WAIT DELAY 1 280 CALL ERASE 290 WAIT DELAY 1 300 FOR x=1 TO 10 310 CALL TextOut(STR$(SQR(x))) 320 NEXT x 330 WAIT DELAY 4 340 ! CALL Run 350 END
340行の CALL Run を有効にすると,ユーザにwindow1を操作させることが可能になり,ウィンドウを閉じるまでBASICプログラムは進行を停止する。