BASICの言語仕様の範囲では実行できないことが,Win32 APIを利用することで実行可能となることがあります。
(仮称)十進BASIC(ver. 5.1)では,Win32 API,および,Win32 APIと同じ呼び出し規約のもとに作成されたDLL関数が利用できます。Borland DelphiやC++などのシステム開発言語を用いて自作したDLLも利用可能です。
Win32 APIの利用には,利用したいAPIの関数名と,それが含まれるDLLのファイル名とが必要です。
また,APIの引数には,ニーモニックではなく実際の数値を与える必要があります。
メモリーを扱う処理は文字列変数を利用して行います。
特に,変数へのポインタを引数として要求するDLL関数の利用には注意が必要です。
使い方を誤るとシステムをクラッシュさせることもあるので,意味の分からない人は手を出してはいけません(サンプルプログラムをコピー&ペーストで貼り付けて実行してみる程度にとどめてください)。
利用者定義関数および副プログラムをDLL関数を利用して定義することができる。内部,外部のどちらでもよいがDEF文には適用できない。
DLLを利用して定義する関数(または,副プログラム)では, FUNCTION行(またはSUB行)と,END FUNCTION行(またはEND SUB行)の間に次の形式のASSIGN文を書く。
ASSIGN DLLファイル名 ,関数名
例
FUNCTION GetVersion ASSIGN "kernel32.dll","GetVersion" END FUNCTION
DLLファイル名は,利用しようとする関数を含むDLLの名称を指定する。関数名は,DLL内で登録されている名称を指定する。関数名は,英字の大小の違いを識別する。
DLLファイル名と関数名は文字列定数で指定する。翻訳時にDLLをロードするので,文字列変数を利用して指定することはできない。
数値型の引数は上位ビットを切り捨てた32ビット整数として評価して渡す。副プログラムの場合であっても変数引数とはならず,常に値引数になる。
文字列型の引数は,1文字目への32ビットポインタとして渡される。BASICの文字列はヌルで終端された文字列として利用できる。
ただし,空文字列を指定したときは,ヌルへのポインタではなく,ヌル(通常,無効ポインタを意味する)を渡す。
数値関数として定義すると,DLLの関数から制御が戻されたときのEAXレジスタの値が関数値となる。この値は,常に32ビットの符号付き整数として解釈する。
文字列関数として定義すると,DLLの関数から制御が戻されたときのEAXレジスタの値をヌル終端のASCII(またはShift-JIS)文字列へのポインタとして解釈する(ver. 5.7.8)。
例1
DECLARE EXTERNAL FUNCTION MesBox LET n=MesBox(0,"Hello","BASIC",3) PRINT n END EXTERNAL FUNCTION MesBox(owner,text$,caption$,flag) ASSIGN "user32.dll","MessageBoxA" END FUNCTION
関数値は32ビットの符号付き整数になる。符号なし整数として解釈したいときは,MOD(x,2^32)を適用する。
Win32 APIで結果の型がBOOLであるものは,非ゼロが真。
結果型が数値でないAPIも,得られた値は別のAPI呼び出しの引数として使うことができる。
Win32 APIで文字列を扱う関数には,ANSI文字列を対象とするものと,16ビットのユニコード文字列を対象とするものの2種があるが,(仮称)十進BASICの内部文字コードはShift-JISなので,ANSI文字列を扱うほう,つまり,関数名の末尾がAになっているほうを用いる。(上の例では,MessageBoxA を指定している)
変数へのポインタを引数に持つDLL関数を呼び出すときは,文字列変数を引数にする。
<Note>サポートDLLを用いてメモリーを直接操作して引数を用意することもできる
( → MEMORY.DLLによる直接メモリー操作)。
文字列をバイト配列とみなすために,必要に応じてOPTION CHARACTER BYTEを宣言する。
変数がDLL関数にパラメータを渡すためにのみ用いられ,DLL関数が変数を書き換えることがない場合は,文字列式を引数としてもよい。
このDLL関数が結果を戻すために変数を書き換えるものであるときは,次の2点に注意する。
REPEAT$関数などを利用して,引数として与える文字列変数の文字数をDLLの関数が必要とする大きさ以上としておく。
この変数の値は他の変数から代入して作成したものであってはならず,また,この変数は他の変数に代入したことのあるものであってもならない。
(他の変数からの代入で値を与えられた文字列変数は,実領域を元の変数と共有する。したがって,この変数の実領域をDLL関数が書き換えると,代入もとの変数の値も書き換わってしまう。)
受け取った値は部分文字列指定を利用して取り出す。
例2
DECLARE EXTERNAL FUNCTION CurrDir$ PRINT CurrDir$ END EXTERNAL FUNCTION CurrDir$ OPTION CHARACTER BYTE FUNCTION GetCurrentDirectory(n,s$) ASSIGN "kernel32.dll","GetCurrentDirectoryA" END FUNCTION LET s$=Repeat$(" ", 200) LET n=GetCurrentDirectory(200,s$) LET CurrDir$=s$(1:n) END FUNCTION
整数型の変数へのポインタを引数とするAPIを呼び出すときも,(仮称)十進BASICでは文字列変数を引数にしなければならない。
たとえば,32ビットの整数型の変数に値を受け取る場合は,
LET s$=Repeat$("#", 4) を実行して4バイトの領域を確保してからDLLに引き渡す。変数引数が複数あるときは,それぞれの変数ごとにRepeat$関数を実行する。
Intel x86系のCPUでは,複数バイトで数値を表すとき,上位桁がアドレス上位に位置
する。文字列変数内で文字はアドレスの昇順に配置されるので,文字列には下位バイトから順に格納されている。
したがって,
結果は,符号なし整数として解釈する場合, ORD(s$(1:1)) + 2^8 * ORD(s$(2:2)) + 2^16 * ORD(s$(3:3)) + 2^24 * ORD(s$(4:4))
。
符号付き整数にしたい場合は,結果が2^31以上であるときに2^32を減ずる。
構造体そのものを引数とする場合は,4バイトごとに区切って順に引数として与える。
構造体を引数として要求するAPIには,構造体へのポインタを引数として与えるものがある。結果を戻す必要がある場合と,必要なメモリの大きさが不定の場合にこの形が用いられる。この場合,必要とされるバイト数の長さの文字列変数を用意し,その内容をAPIの要求に合わせて引数として渡す。
32ビット整数nを文字列変数に埋め込むのに,次の外部関数定義が利用できる。
Note. Ver. 7.4.8以降では同じ効果を持つ組込関数DWORD$があらかじめ用意されている。
EXTERNAL FUNCTION DWORD$(n) OPTION CHARACTER byte LET r=MOD(n,2^8) LET s$=CHR$(r) LET n=(n-r)/2^8 LET r=MOD(n,2^8) LET s$=s$ & CHR$(r) LET n=(n-r)/2^8 LET r=MOD(n,2^8) LET s$=s$ & CHR$(r) LET n=(n-r)/2^8 LET r=MOD(n,2^8) LET DWORD$=s$ & CHR$(r) END FUNCTION
32ビット整数が連なった構造体の場合は,この関数の結果を連結すればよい。
結果を取り出す場合は,上述の整数型の変数へのポインタを引数とする場合と同様。
文字列に埋め込まれた32ビット符号付き整数を取り出す外部関数を例として示す。
引数pは,文字列内のバイト単位での位置を表す。先頭を0とする。
EXTERNAL FUNCTION int32(s$,p) OPTION CHARACTER byte LET n=0 FOR i=1 TO 4 LET n=n+256^(i-1)*ORD(s$(p+i:p+i)) NEXT i IF n<2^31 THEN LET int32=n ELSE LET int32=n-2^32 END FUNCTION
16ビット整数nを文字列変数に埋め込むときは,次の外部関数定義が利用できる。
Note. Ver. 7.4.8以降には,同じ効果を持つ組込関数WORD$があらかじめ用意されている。
EXTERNAL FUNCTION WORD$(n) OPTION CHARACTER byte LET r=MOD(n,2^8) LET s$=CHR$(r) LET n=(n-r)/2^8 LET r=MOD(n,2^8) LET WORD$=s$ & CHR$(r) END FUNCTION
16ビット整数が連なった構造体の場合は,この関数の結果を連結する。
文字列に埋め込まれた16ビット符号付き整数を取り出す外部関数を次に示す。
引数pは,文字列内のバイト単位での位置を表す。先頭を0とする。
EXTERNAL FUNCTION int16(s$,p) OPTION CHARACTER byte LET n=0 FOR i=1 TO 2 LET n=n+256^(i-1)*ORD(s$(p+i:p+i)) NEXT i IF n<2^15 THEN LET int16=n ELSE LET int16=n-2^16 END FUNCTION
WIN32 APIには,構造体に文字列へのポインタを置くものがある。
文字列変数の実アドレス(最初の文字へのポインタ)を取得する関数は,CharPrevを使って作れる。
EXTERNAL FUNCTION VarPtr(s$) FUNCTION CharPrev(s$,t$) ASSIGN "user32.dll","CharPrevA" END FUNCTION LET VarPtr=CharPrev(s$,s$) END FUNCTION
<Note>(仮称)十進BASICでは,文字列変数の実アドレスは,文字列変数に固有のものではなく,変数値の書き換えがあると変化する。
Win32 APIには,コールバック関数を要求するものがある。
(仮称)十進BASIC(ver.5.3.2以降)では,最大10個までのCallBack関数を使うことができ,それらは,0から9までの番号で識別する。
CallBack関数の指定
CallBack関数として利用する手続きのFUNCTION行またはSUB行の末尾に次の形式でCallBack関数の識別番号を指定する。識別番号は0から9までの定数でなければならない。
FUNCTION 関数名(引数リスト),CALLBACK 識別番号
SUB 副プログラム名(引数リスト),CALLBACK 識別番号
引数はすべて値引数になる。ヌル終端文字列へのポインタは文字列変数に受けとることができる。
それ以外の引数はすべて32ビット符号付整数として数値変数名に割り当てなければならない。
(ヌル終端文字列へのポインタ以外のポインタを文字列変数で受けてはいけない。)
32ビットを超える大きさを持つ引数は,32ビットごとに分割して数値変数で受け取る。
コールバック関数の全引数(埋め草を含む)のバイト数と,引数リストに書いた引数の数の4倍とが一致しなければならない。
真偽値は数値変数で受け取る。非ゼロを真と解釈すればよい。
真偽値を関数の結果(戻り値)として要求されたときは,真のとき1(または-1),偽のとき0を返せばよいのが普通。
CallBack関数のアドレスの取得
CallBack関数のアドレスはCallBackAdr関数で取得する。引数は,CallBack関数の識別番号。
使用例
100 OPTION CHARACTER BYTE 110 LET Name$ = REPEAT$(CHR$(0),255 120 CALL EnumWindows(CallBackAdr(9), 0) 130 SUB EnumWindows(IpEnumFunc, IPalam) 140 ASSIGN "user32.dll","EnumWindows" 150 END SUB 160 FUNCTION GetWindowText(hWnd,IpString$,cch) 170 ASSIGN "user32.dll","GetWindowTextA" 180 END FUNCTION 190 FUNCTION Enum(Handle), CALLBACK 9 200 IF GetWindowText(Handle, Name$, LEN(Name$))<>0 THEN 210 PRINT NAME$(1:POS(NAME$,CHR$(0))-1) 220 END IF 230 LET Enum = -1 240 END FUNCTION 250 END
注意
CallBack関数を非同期の呼び出しに利用しないでください。
引数の個数の誤りは翻訳時にも実行時にもチェックされません。引数の個数の誤りは,システムに重大な損傷を与える可能性があります。
DLLが別のDLLにある関数を呼び出すとき,あらかじめそのDLLを読み込んでおく必要があるかも知れない。
その場合,Dll名を引数にしてLoadLibraryを実行し,その結果(ハンドル)を数値変数に保存(ただし,この値が0のとき,LoadLibraryは失敗している)。その値と関数名をGetProcAddressに渡してアドレスを取得する。必要なくなったら,保存しておいたDLLのハンドルを引数にしてFreeLibraryを呼び出す。
FUNCTION LoadLibrary(s$) ASSIGN "kernel32.dll","LoadLibraryA" END FUNCTION FUNCTION GetProcAddress(n,s$) ASSIGN "kernel32.dll","GetProcAddress" END FUNCTION SUB FreeLibrary(n) ASSIGN "kernel32.dll","FreeLibrary" END SUB
Full BASICの組込み関数を利用して,2進,16進表現された数値を扱うことができる。
BVAL(a$,2) a$を非負の2進整数として解釈した値。 BVAL(a$,16) a$を非負の16進整数として解釈した値。 BSTR$(n,2) 非負の整数nを2進数として表現した文字列。 BSTR$(n,16) 非負の整数nを16進数として表現した文字列。
例 BSTR$(MOD(n, 2^32),16)
32ビットの符号つき整数nを符号なし整数の16進表記文字列に変換する。
DLLには引数の受け渡し方や引数の個数などに関する情報が含まれていないから,引数の個数の誤りは翻訳時にも実行時にもチェックすることができない。(一見,正常に動作しているように見えることもあるので,注意。)
変数引数として用いる文字列の長さが不足していたとき,DLL関数の実行自体は正常に行われる可能性が高い。しかし,他の変数あるいはBASICのシステムが使用する領域が破壊されるため,以後の動作に支障をきたす。DLL関数の呼び出しは正常に行われるため,原因を見つけにくいバグになる。
数値変数へのポインタを指定すべきところに数値変数を指定すると,その値をアドレスとして解釈し,そこにDLL関数がデータを書き戻すことになる。(運がいいとextype=-9900のエラーになるが,運悪くそこがBASICのデータ領域だとエラーにならずに実行されてしまう)
Visual BASICで使うためのWin32 API情報は,(仮称)十進BASICで使うために必要な情報がすべて含まれているのですぐに使える。
(ただし,VBで使えるAPIがすべて十進BASICで使えるわけではない。)
WinAPI Database for VB Programmer
API別 Win32 サンプル集
Programming Library Visual BASIC
VBWin32API関数宣言モジュール
Visual Basic最新リンク2005
次は,VBで使うための解説ではないが,DLL名が明示されている。
Win32 API 関数リスト
以下は,C言語によるWin32 APIの解説。目的のAPIがどのDLLに含まれるかを,別途,調べる必要がある。
Win32 API入門
Visual BASIC向けの情報をもとに関数定義を作る場合は次のようにする。
例
Declare Function PolyBezierTo Lib "gdi32" ( ByVal hDC As Long, ByRef Points As POINT, ByVal PointNum As Long) As Long
FUCTION PolyBezierTo(hDC, Points$, PointNum) ASSIGN "gdi32.dll","PolyBezierTo" END FUNCTION
例
Declare Function FindFirstFile Lib "kernel32" Alias "FindFirstFileA" ( ByVal FileName As String,
ByRef FindData As WIN32_FIND_DATA) As Long
FUNCTION FindFirstFile(FileName$, FindData$) ASSIGN "kernel32.dll","FindFirstFileA" END FUNCTION
例
Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Boolean
FUNCTION CloseHandle(hObject) ASSIGN "kernel32.dll","CloseHandle" END FUNCTION
Libに続けて指定されているのがDLL名。".dll"を付加して指定する。
Aliasに続けて指定されているのが,DLL関数名。Aliasがないときは,関数名がそのままDLL関数名。
ByVal ・・・ As Long(あるいは,ByVal ・・・ As Integer,ByVal ・・・ As Byte)となっている変数には数値変数名を指定する。
ByVal ・・・ As String となっている変数には文字列変数名を指定する。
ByRef ・・・ As ・・・ となっている変数には文字列変数名を指定する。
ByVal ・・・ As Any となっている変数は,使い方によってアドレスを代入したり,数値を代入したりするので,目的によって文字列変数,数値変数を使い分ける。なお,関数名を変えれば,同じAPI関数を使う関数定義が複数あっても問題ない。
ByVal ・・・ As String となっている場合でも,結果を書き戻すために文字列変数が使われている場合は,変数引数の場合の注意が当てはまる。
<Note>Visual BASIC 6 で,ByValもByRefも指定されない引数はByRef(参照渡し)。ただし,ByValに続けて複数の引数を書いたとき,それらにはByValが仮定される。
Functionの結果の型が何であっても,数値関数として定義する。
結果型がBooleanのときは,非ゼロが得られたとき真と解釈すればよい。
結果型がStringの場合,BASICでそれを文字列として利用することはできないが,別のAPI関数の引数として利用することはできる。そうしたいとき,仮引数は数値変数で宣言しておく。
関数の戻り値を利用しないDLL関数は副プログラムにしてもよい。
例
SUB CharUpper(s$) ASSIGN "user32.dll","CharUpperA" END SUB
<Memo>CharUpperを数値関数として宣言したとき,実引数に文字列式を与えると,結果は文字列式を評価するのに使用した作業領域を指すが,この領域は,関数定義の実行中に解放されてしまうので,この値は,得られた時点で意味を失っている。
VBのintegerは16ビット符号付き整数,longは32ビット符号付き整数。
例
Type POINTL x As Long y As Long End Type
POINTL型は8バイトを占有する。
POINTL型の変数を文字列変数s$に埋め込むときは,s$に DWORD$(x) & DWORD$(y)を連結すればよい。
例
Type POINTS x As Integer y As Integer End Type
POINTS型は4バイトを占有する。
POINTS型の変数を文字列変数s$に埋め込むときは,s$に WORD$(x) & WORD$(y)を連結すればよい。
例
Type OUTPUT_DEBUG_STRING_INFO lpDebugStringData As String fUnicode As Integer nDebugStringLength As Integer End Type
Stringは文字列へのポインタで4バイトを占有する。
<注意>
4バイトの変数は必ず4の倍数のアドレスに置かれるなどの規則があるために,前方から順にサイズを加算していった場合と位置が異なることがある(メモリ上に無駄な領域ができる)。
たとえば,1バイトの変数の次に4バイトの変数をつなげるときは,間に3バイトの埋め草が入る。
例
Type ACE_HEADER AceType As Byte AceFlags As Byte AceSize As Long End Type
ACE_HEADER型は8バイトを占有し,AceFlagsとAceSizeの間に2バイトの埋め草が入る。
Visual BASICの&H〜〜は16進数を意味する。末尾の&はlong型の意味(無視しても問題ない)
例 &H1000は,BVAL("1000",16)に書き変える。
例 12&は,普通に12と解釈する。16進数ではない。
十進BASICには定数宣言の構文はないので,変数への代入に置き換える。
例 Public Const HELP_COMMAND = &H102&
は,LET HELP_COMMAND = BVAL("102",16)
に変える。
無論,LET HELP_COMMAND = 258
でもよい。
Windowsのコンソールを入出力に利用する。
コンソール出力の後,コンソール入力を受け付ける。文字をいくつか入力してエンターキーを押す。
100 DECLARE EXTERNAL FUNCTION GetStdHandle 110 DECLARE EXTERNAL SUB AllocConsole,FreeConsole,WriteFile,CloseHandle 120 LET o$=REPEAT$("#",4) ! 4バイト確保 130 CALL AllocConsole 140 LET STD_INPUT_HANDLE=-10 150 LET STD_OUTPUT_HANDLE=-11 160 LET n=GetStdHandle(STD_OUTPUT_HANDLE) 170 FOR i=1 TO 30 ! 出力文字列の作成 180 LET s$=STR$(i) & " " & STR$(SQR(i)) & CHR$(13) & CHR$(10) 190 CALL WriteFile(n, s$ ,LEN(s$),o$,0) 200 NEXT i 210 CALL CloseHandle(n) 220 LET n=GetStdHandle(STD_INPUT_HANDLE) 230 LET inp$=REPEAT$("#",1) ! 1バイト確保 240 DO 250 CALL ReadFile(n,inp$,1,o$,0) ! キー入力 260 PRINT inp$(1:1); 270 IF inp$=CHR$(10) THEN EXIT DO ! エンターキーを押すと終わり 280 LOOP 290 CALL CloseHandle(n) 300 CALL FreeConsole 310 END 320 EXTERNAL FUNCTION GetStdHandle(n) 330 ASSIGN "kernel32.dll","GetStdHandle" 340 END FUNCTION 350 EXTERNAL SUB AllocConsole 360 ASSIGN "kernel32.dll","AllocConsole" 370 END SUB 380 EXTERNAL SUB FreeConsole 390 ASSIGN "kernel32.dll","FreeConsole" 400 END SUB 410 EXTERNAL SUB WriteFile(n,s$,LENGTH,o$,a) 420 ASSIGN "kernel32.dll","WriteFile" 430 END SUB 440 EXTERNAL SUB ReadFile(n,s$,LENGTH,o$,a) 450 ASSIGN "kernel32.dll","ReadFile" 460 END SUB 470 EXTERNAL SUB CloseHandle(n) 480 ASSIGN "kernel32.dll","CloseHandle" 490 END SUB
Windows APIのcreateFileを直接用いてファイルを読む。
100 OPTION CHARACTER BYTE 110 DECLARE EXTERNAL FUNCTION CreateFile 120 DECLARE EXTERNAL SUB ReadFile,CloseHandle 130 LET Name$="C:\BASICw32\README.TXT" ! 実在のテキストファイルを指定する 140 LET GENERIC_READ=BVAL("80000000",16) 150 LET OPEN_EXISTING=3 160 LET FILE_ATTRIBUTE_NORMAL=BVAL("80",16) 170 LET s$=REPEAT$("#",1) ! 1バイト確保 180 LET w$=REPEAT$("#",4) ! 4バイト確保 190 LET hfile=CreateFile(Name$,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0) 200 IF hfile<>-1 THEN 210 DO 220 LET t$="" 230 DO 240 CALL ReadFile(hfile,s$,1,w$,0) 250 IF s$=CHR$(13) THEN ! chr$(13)の次はchr$(10)であると仮定 260 CALL ReadFile(hfile,s$,1,w$,0) 270 EXIT DO 280 END IF 290 IF ORD(w$(1:1))=0 THEN EXIT DO 300 LET t$ = t$ & s$(1:1) 310 ! 上の文の(1:1)がないと,t$=""のとき,s$がt$に代入されてしまう。 320 LOOP 330 PRINT t$ 340 IF ORD(w$(1:1))=0 THEN EXIT DO 350 LOOP 360 CALL closeHandle(hfile) 370 ELSE 380 PRINT "エラー" 390 END IF 400 END 410 420 EXTERNAL FUNCTION CreateFile(Name$, acc ,share, sec, create, attrib, handle) 430 ASSIGN "kernel32.dll","CreateFileA" 440 END FUNCTION 450 460 EXTERNAL SUB ReadFile(handle, s$, l, w$, ov ) 470 ASSIGN "kernel32.dll","ReadFile" 480 END SUB 490 500 EXTERNAL SUB CloseHandle(handle) 510 ASSIGN "kernel32.dll","CloseHandle" 520 END SUB
哲さんからの情報です。
100 DECLARE EXTERNAL FUNCTION FndWnd 110 DECLARE EXTERNAL FUNCTION ShowWnd 120 LET hWnd=FndWnd("TTextForm","") 130 IF hWnd>0 THEN 140 LET n=ShowWnd(hWnd, 3) 150 END IF 160 ! ここに必要な処理(PRNIT文,etc)を書く 170 END 180 190 EXTERNAL FUNCTION FndWnd(lpClassName$,lpWindowName$) 200 ASSIGN "user32.dll","FindWindowA" 210 END FUNCTION 220 230 EXTERNAL FUNCTION ShowWnd(hWnd, nCmdShow) 240 ASSIGN "user32.dll","ShowWindow" 250 END FUNCTION
ShowWnd(hWnd, 3) の第2引数は HIDE = 0 NORMAL = 1 SHOWMINIMIZED = 2 MAXIMIZE = 3 SHOWNOACTIVATE = 4 SHOW = 5 MINIMIZE = 6 SHOWMINNOACTIVE = 7 SHOWNA = 8 RESTORE = 9 SHOWDEFAULT = 10 FORCEMINIMIZE = 11
FndWndの第1パラメータは,描画ウィンドウでは,"TPaintForm",Inputダイアログでは"TInputDialog"です。
BASICを複数起動しているときは,FndWndの第2パラメータに,ウィンドウの上端に表示されるウィンドウの名前(プログラム名の末尾を.TXTまたは.BMPに変えたもの)を指定します。
<補足>CHARACTER INPUT文の実行時に表示されるウィンドウは"TCharInput"です。
DECLARE EXTERNAL FUNCTION midiOpen DECLARE EXTERNAL FUNCTION midiMsg DECLARE EXTERNAL FUNCTION midiClose ! 利用可能なMIDIデバイスの数を獲得 DECLARE EXTERNAL FUNCTION midiGet OPTION CHARACTER byte ! 文字サイズを1バイトにする LET hmid$=REPEAT$("*",4) ! 四バイト分用意 LET Note=70 !音階(&H00から&H7F(127)) LET Inst=0 !楽器No(GM音色番号に準拠?) LET Vol=BVAL("7F",16) !音量(&H00から&H7F(127)) IF midiGet<1 THEN STOP ! MIDIデバイスがなければ終了 LET n=midiOpen(hMid$, -1, 0, 0, 0) ! 文字列を数値に変換 LET hMidiOut=0 LET k=1 FOR i=1 TO 4 LET hMidiOut = hMidiOut + k*ORD(hMid$(i:i)) LET k = k*256 NEXT i LET n=midiMsg(hMidiOut, BVAL("c0",16) + Inst * 256) LET n=midiMsg(hMidiOut, BVAL("90",16) + Note * 256 + Vol * 256 * 256) WAIT DELAY 1 LET n=midiMsg(hMidiOut, BVAL("80",16) + Note * 256) LET n=midiClose(hMidiOut) END EXTERNAL FUNCTION midiOpen(lphMidiOut$,uDeviceID,dwCallback,dwInstance,dwFlags) ASSIGN "winmm.dll","midiOutOpen" END FUNCTION EXTERNAL FUNCTION midiMsg(hMid,Dt) ASSIGN "winmm.dll","midiOutShortMsg" END FUNCTION EXTERNAL FUNCTION midiClose(hMid) ASSIGN "winmm.dll","midiOutClose" END FUNCTION EXTERNAL FUNCTION midiGet ASSIGN "winmm.dll","midiOutGetNumDevs" END FUNCTION
DECLARE EXTERNAL FUNCTION PlaySound LET S$="***.wav" LET n=PlaySound(S$, "", 128) END EXTERNAL FUNCTION PlaySound(pszSound$, hmod$, fdwSound) ASSIGN "winmm.dll","PlaySoundA" END FUNCTION
Visual BASICやC言語用に作られたDLLを使うこともできる。
8ビット,あるいは16ビットの引数は,スタックには32ビット境界にあわせてセットされるため,それらの引数に32ビット値を与えても支障はない(上位ビットが無視される)。
結果型が8ビット,あるいは16ビットである関数の値を使うときは,MOD(x,2^8)またはMOD(x,2^16)を適用して上位ビットを切り捨てる。Visual BASICのBoolean型は16ビットなので,Visual BASIC専用に作られたDLLを使う場合は注意が必要かもしれない。
ディスパッチインターフェースに対応しないCOMコンポーネント,画面表示を伴うActiveXなどは,VB,Delphi,C++など,他言語でDLLを書いて利用する。
<Note>stdcall(Win32 APIの呼び出し規約)でコンパイルされた関数が対象であるが,cdecl(C言語の標準の呼び出し規約)でコンパイルされた関数を呼び出してもとりあえず動く。
<Note>C言語で作成されたDLLには,DLL内で登録されている関数名がマニュアルに書いてあるのと違うものがある。
<Note>DLLをロードできないエラーになるときは,DLLをフルパス名で指定するか,DLLをpathの通ったフォルダに置く,あるいは,プログラムをDLLがあるのと同じフォルダに保存してから実行する。
UNHLA32.DLLは,マニュアルがC言語向けに書かれているが,使える。
最も単純なLHA互換機能だけだと次のようになる。
100 REM UNLHA32.DLLをLHAとして使う 110 SUB UNLHA(HWND,CMD$,BUF,NBUF) 120 ASSIGN "UNLHA32.DLL","Unlha" 130 END SUB 140 SUB LHA(CMD$) 150 CALL UNLHA(0,CMD$,0,0) 160 END SUB 170 SET DIRECTORY "c:\****" ! カレント・ディレクトリを移動する 180 CALL LHA("a ****** *****.txt") ! 引数に,LHAのコマンドを指定する 190 END
VBIOSCMは,VB6用となっているが,単純に読み替えるだけで使える。
Visual BASICの x and 1 は,MOD(x,2)に置き換えればよい。
サンプルプログラム(Windows MeとWindows XP(SP2)で動作を確認。IBM PC互換機以外では絶対に実行しないでください。)
DECLARE EXTERNAL FUNCTION IOSCM_Start, InpB, InpD, InpW DECLARE EXTERNAL SUB IOSCM_Stop, OutB, OutD, OutW REM DOS/V機でビープ音を鳴らす IF MOD(IOSCM_Start,2)=0 THEN LET port=BVAL("61",16) LET n=InpB(port) CALL OutB(port,INT(n/4)*4+3) !鳴らす PAUSE CALL OutB(port,INT(n/4)*4) !止める CALL IOSCM_Stop ELSE PRINT "IOSCM Init Error!!" END IF END EXTERNAL FUNCTION InpB (port) FUNCTION InpB_org(port) ASSIGN "VBIOSCM_DLL.DLL", "_InpB@4" END FUNCTION LET InpB=MOD(InpB_org(port),2^8) ! 下位8ビットを取り出す END FUNCTION EXTERNAL FUNCTION InpW (port) FUNCTION InpW_org(port) ASSIGN "VBIOSCM_DLL.DLL", "_InpW@4" END FUNCTION LET InpW=MOD(InpW_org(port),2^16) ! 下位16ビットを取り出す END FUNCTION EXTERNAL FUNCTION InpD(port) ASSIGN "VBIOSCM_DLL.DLL", "_InpD@4" END FUNCTION EXTERNAL SUB OutB(port, dat) ASSIGN "VBIOSCM_DLL.DLL", "_OutB@8" END SUB EXTERNAL SUB OutW(port, dat) ASSIGN "VBIOSCM_DLL.DLL", "_OutW@8" END SUB EXTERNAL SUB OutD(port, dat) ASSIGN "VBIOSCM_DLL.DLL", "_OutD@8" END SUB EXTERNAL FUNCTION IOSCM_Start ASSIGN "VBIOSCM_DLL.DLL", "_IOSCM_Start@0" END FUNCTION EXTERNAL SUB IOSCM_Stop ASSIGN "VBIOSCM_DLL.DLL", "_IOSCM_Stop@0" END SUB
同種のDLLに,Robot I/O Port 32 DLL がある。こちらは,有料だけれど,パラレルポートにも対応している。テストはしていないが,Readme.txtを読むかぎりは使えるはず。
jconv.dllは, DLLに登録された関数の名前がマニュアルの記述と異なるという特徴が現れたDLLである。
まず,jconv.hを見ると,
typedef enum { JC_AUTO, JC_UTF_7, JC_ISO2022JP, JC_UTF_8, JC_SHIFT_JIS, JC_EUC_JP, JC_UTF_16_B, JC_UTF_16_L, JC_UCS_4_B } JC_JCODE_TYPE;
とある。これは,JC_JCODE_TYPE型の定義で,JC_AUTO,JC_UTF_7,・・・に,0から始まる数値が順に割り当てられていることを意味する。BASICでは,それぞれに順に0から始まる数値を代入しておけば使いやすい。
DLL内のメインの関数jconvを使うために
FUNCTION jconv(InType,In$,InLen,OutType,Out$)
ASSIGN "jconv.dll","jconv"
END FUNCTION
とすると,翻訳時に「jconvがみつからない」のエラーになる。(DLLはロードできたけれども,関数のエントリーポイントがみつからないということ)
DLL Analyze AZUKIで調べると,関数名のjconvが _jconv@20 に変わってしまっているらしいことがわかる。
jconv.dllの使用サンプル(shift−JIS文字列をWindowsのユニコード文字列に変換)
100 OPTION CHARACTER BYTE 110 SUB MesBoxW(owner,text$,caption$,flag) 120 ASSIGN "user32.dll","MessageBoxW" 130 END SUB 140 FUNCTION jconv(InType,In$,InLen,OutType,Out$) 150 ASSIGN "jconv.dll","_jconv@20" 160 END FUNCTION 170 LET JC_AUTO=0 180 LET JC_UTF_7=1 190 LET JC_ISO2022JP=2 200 LET JC_UTF_8=3 210 LET JC_SHIFT_JIS=4 220 LET JC_EUC_JP=5 230 LET JC_UTF_16_B=6 240 LET JC_UTF_16_L=7 250 LET JC_UCS_4_B=8 260 LET s1$="こんにちは" 270 LET s2$="BASIC" 280 LET t1$=REPEAT$("##",LEN(s1$)+1) ! 結果を受け取るための作業領域 290 LET t2$=REPEAT$("##",LEN(s2$)+1) ! 結果を受け取るための作業領域 300 LET text$=t1$(1:jconv(JC_SHIFT_JIS,s1$,LEN(s1$),JC_UTF_16_L,t1$)) 310 LET caption$=t2$(1:jconv(JC_SHIFT_JIS,s2$,LEN(s2$),JC_UTF_16_L,t2$)) 320 CALL MesBoxW(0,text$,caption$,3) 330 END
<教訓>Visual C++で作成されたDLLで関数名を認識しないときは,関数名の前に_(アンダースコア),関数名の後に@と引数の個数を4倍した数(=引数のバイト数)を付加してみる。
<注意> 空文字列は空文字列へのポインタではなくnullになるので,空文字列をjconvに渡すとエラーになる。
<補足>shift-JIS文字列をWindowsのユニコード文字列に変換したいだけならWin32 APIでできる。 この手のDLLが有用なのは,UTF-8との変換ができること。
このDLLはC言語の呼び出し規約_cdeclであるが,使える。
このDLLは実数値の入出力を構造体上で行う。引数は構造体へのポインタである。
本BASICでは構造体のバイト数の文字列を確保し,そのアドレスをDLLに渡す。
Double型実数値を構造体上にセットするのに組込関数PackDBL$を使う。
180行のPackDBL$(x)は,xをDouble型に変換して8バイトの文字列に埋め込む。
190行のUnPackDBL関数はその逆の操作を行う。
利用者定義関数のDWord$とVarPtrはLibraryフォルダのAPISUP1.LIBで定義されているので,MERGE文で読み込んで利用する。
100 DECLARE EXTERNAL FUNCTION DWord$, VarPtr 110 OPTION CHARACTER byte 120 FUNCTION StrCalc(PSTRCALC_PARAM$) 130 ASSIGN "StCalc35.dll","StrCalc" 140 END FUNCTION 150 LET x=2.1 160 LET y=3.3 170 LET s$="5*x-4*y" 180 LET Param$=DWord$(VarPtr(s$)) & REPEAT$(CHR$(0), 4+4+4) & PackDBL$(x) & PackDBL$(y) & REPEAT$(CHR$(0),8) 190 IF StrCalc(Param$)=0 THEN PRINT UnPackDBL(Param$(33:40)) 200 END 210 MERGE "APISUP1.LIB"
結果の型がDoubleなどの実数値を返す関数をBASICの関数に割り当てるためには,
ASSIGN DLLファイル名 ,関数名 ,FPU
の形のASSIGN文を書く。
この形のASSIGN文を実行すると,FPUのスタックトップレジスタをPOPしてその値をBASICの関数値とする。
このDLLには,
の2つの関数が定義されているので,ここでは,下のVB用を利用する。
__declspec(dllexport) double Kaiseki(char *mojiretu, int *ERR); C言語用
__declspec(dllexport) double __stdcall Kaiseki_VB(char *mojiretu, int *ERR); VB用
この関数は,内部で名前が _Kaiseki_VB@8 と変わってしまっている。
BASICでは次のように使う。
100 FUNCTION kaiseki(s$,e$) 110 ASSIGN "EpMath.dll", "_Kaiseki_VB@8", FPU 120 END FUNCTION 130 INPUT s$ 140 LET e$=REPEAT$("1234",1) ! 4バイト確保 150 LET a=kaiseki(s$,e$) 160 IF ORD(e$(1:1))=0 THEN 170 PRINT a 180 ELSE 190 PRINT "error";ORD(e$(1:1)) 200 END IF 210 END
山本秀樹氏により作成された Windows上で利用できるMersenne Twister法による擬似乱数列生成ルーチン。
ホームページに Visual BASIC用の宣言例があるので,機械的に翻訳するだけで利用できる。
100 DECLARE EXTERNAL FUNCTION random 110 DECLARE EXTERNAL SUB randomize 120 CALL randomize(567) 130 FOR i=1 TO 100 140 PRINT USING "#.###############": random 150 NEXT i 160 END 170 EXTERNAL FUNCTION random 180 ASSIGN "libMT.DLL" ,"genrand" , FPU 190 END FUNCTION 200 EXTERNAL SUB randomize(seed) 210 ASSIGN "libMT.DLL", "sgenrand" 220 END SUB
このDLLにはVB用のサンプルが付属するので,機械的に翻訳すれば使える。
このライブラリには全部で4つのDLLが含まれるが,そのうち,mt19937m.dllは,上と同様に使える。
100 DECLARE EXTERNAL FUNCTION random 110 DECLARE EXTERNAL SUB randomize 120 CALL randomize(4357) 130 FOR i=1 TO 100 140 PRINT USING "#.###############": random 150 NEXT i 160 END 170 EXTERNAL FUNCTION random 180 ASSIGN "mt19937m.dll" ,"genrandm" ,FPU 190 END FUNCTION 200 EXTERNAL SUB randomize(seed) 210 ASSIGN "mt19937m.dll","sgenrandm" 220 END SUB
mt19937ar.dllの場合は次のようになる。
不定長整数配列への参照引数を要求するinit_by_arrayは文字列変数を引数にして利用する。
100 DECLARE EXTERNAL FUNCTION genrand_real2, genrand_int32 110 DECLARE EXTERNAL SUB init_by_array, init_genrand 120 LET init_key$=REPEAT$(CHR$(0),16) ! DIM init_key(3) As Long 130 LET init_key$( 1: 2)=CHR$(BVAL("23",16)) & CHR$(BVAL("01",16)) ! init_key(0) = &H123 140 LET init_key$( 5: 6)=CHR$(BVAL("34",16)) & CHR$(BVAL("02",16)) ! init_key(1) = &H234 150 LET init_key$( 9:10)=CHR$(BVAL("45",16)) & CHR$(BVAL("03",16)) ! init_key(2) = &H345 160 LET init_key$(13:14)=CHR$(BVAL("56",16)) & CHR$(BVAL("04",16)) ! init_key(3) = &H456 170 CALL init_by_array(init_key$, 4) ! init_by_array init_key(0), 4 180 ! CALL init_genrand (4357) ! 'init_genrand (4357) 190 FOR j = 1 TO 100 200 PRINT USING "#.###############": genrand_real2 210 NEXT j 220 END 230 1000 ! Public Declare Function genrand_real2 Lib "mt19937ar" () As Double 1010 EXTERNAL FUNCTION genrand_real2 1020 ASSIGN "mt19937ar.dll", "genrand_real2" ,FPU 1030 END FUNCTION 1040 1100 ! Public Declare Function genrand_int32 Lib "mt19937ar" () As Long 1110 EXTERNAL FUNCTION genrand_int32 1120 ASSIGN "mt19937ar.dll" , "genrand_int32" 1130 END FUNCTION 1140 1200 ! Public Declare Sub init_by_array Lib "mt19937ar" (ByRef init_key As Long, ByVal key_length As Long) 1210 EXTERNAL SUB init_by_array (init_key$, key_length) 1220 ASSIGN "mt19937ar.dll", "init_by_array" 1230 END SUB 1240 1300 ! Public Declare Sub init_genrand Lib "mt19937ar" (ByVal seed As Long) 1310 EXTERNAL SUB init_genrand(seed) 1320 ASSIGN "mt19937ar.dll", "init_genrand" 1330 END SUB
<補足>
機能語RNDはFull BASICの予約語なので,乱数の名前をRNDに変えて使うことはできませんが,それ以外であれば,RANDOMなど,適宜,別の名前に変えて使うことが可能です。
<Note>
DLLをロードできないエラーになるときは,DLLをフルパス名で指定するか,プログラムをDLLがあるのと同じフォルダに保存してから実行してください。