Win32 APIとDLLの利用 

 BASICの言語仕様の範囲では実行できないことが,Win32 APIを利用することで実行可能となることがあります。

 (仮称)十進BASIC(ver. 5.1)では,Win32 API,および,Win32 APIと同じ呼び出し規約のもとに作成されたDLL関数が利用できます。Borland DelphiやC++などのシステム開発言語を用いて自作したDLLも利用可能です。
 Win32 APIの利用には,利用したいAPIの関数名と,それが含まれるDLLのファイル名とが必要です。 また,APIの引数には,ニーモニックではなく実際の数値を与える必要があります。

メモリーを扱う処理は文字列変数を利用して行います。
特に,変数へのポインタを引数として要求するDLL関数の利用には注意が必要です。

使い方を誤るとシステムをクラッシュさせることもあるので,意味の分からない人は手を出してはいけません(サンプルプログラムをコピー&ペーストで貼り付けて実行してみる程度にとどめてください)。


外部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を読み込んでおく必要があるかも知れない。
その場合,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

2進,16進表現

 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向けAPI情報の利用

 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

サンプル(2)

 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

十進BASIC掲示板より

 哲さんからの情報です。

実行結果(テキスト出力)のウィンドウを最大化する

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"です。

MIDI音源を利用して単音を出す

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 

WAVファイルを演奏する

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 

汎用DLLを使う

 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があるのと同じフォルダに保存してから実行する。

UNLHA32.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を使う

 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を読むかぎりは使えるはず。

jconvlibを使う

 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との変換ができること。

Double型実数を変数引数にとるDLL

数式文字列の計算 文字列による数式を計算するDLL

この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" 

実数値を返すDLL関数

 結果の型がDoubleなどの実数値を返す関数をBASICの関数に割り当てるためには,
ASSIGN DLLファイル名 ,関数名 ,FPU
の形のASSIGN文を書く。
この形のASSIGN文を実行すると,FPUのスタックトップレジスタをPOPしてその値をBASICの関数値とする。

例 EPMATH

このDLLには,
__declspec(dllexport) double Kaiseki(char *mojiretu, int *ERR); C言語用
__declspec(dllexport) double __stdcall Kaiseki_VB(char *mojiretu, int *ERR); VB用
の2つの関数が定義されているので,ここでは,下の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



例 Mersenne Twister

山本秀樹氏により作成された 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

例 Mersenne Twister DLL版

この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があるのと同じフォルダに保存してから実行してください。



戻る    DLLを自作する