十進BASICの限界

 投稿者:名無しさん  投稿日:2019年12月25日(水)01時16分21秒
   私の前回のレスのおかげで、この掲示板にDLLブウムが起きた(らしい)ことは、嬉しいことです。
 こういう知性系掲示板と言うのは質問以外は、なかなか反応が分かる書き込みが無いものです(ちゃっかり黙ってレスのプログラムだけコピイする者はいるだろーが)。よってちょっとでも反応らしいことが起きると嬉しいものですね♪
 さて、このレスの結論から先に言えば、DLL関数使用時、MSO VBA etc.では4バイト小数型変数が使えるが、十進BASICでは使えない。
 グルグルSMF内のGGS4SETMASTERTEMPOと言う関数を例に挙げよう。
 グルグルSMF(以下GGSと呼ぶ)というのは、MIDI演奏するための無料のDLLであって、その名の通りルウプ演奏に長けたDLLである。そのほかにSMFのヘエドイン、ヘエドアウト演奏、演奏中のボリュウムダウン、テンポ変更、ピッチ変更etc.の機能がある。
 このソフトのヘルプによればC系言語向けの実装と言うことになっているが、BASICでも一応使用が可能。
URLは コチラ↓
ttp://gurugurusmf.migmig.net/

 問題のGGS4SETMASTERTEMPO関数は以下の仕様となっている。

void GGS4SetMasterTempo(float tempo);  // C

再生中にテンポを動的に変化させます。
デバイスを閉じるまで有効です。

float tempo
 変化割合を指定します。
 1で通常、0.5で半分、2で2倍のテンポになります。
 現在、0.1~8までの範囲が有効です。

 まず、この関数をEXCEL VBA 2000に適用してみる。float型変数はVBAでは8バイト小数、すなわちdouble型変数(だと思っていた)なのでas doubleを宣言した。なお、BASIC系言語では、GGS4を頭に冠した書式の関数しか使用できない。

Private Declare Function op Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4OpenDevice"(Byval q as Long,Byval w as Long) as Long
Private Declare Function al Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4AddListFromFileA"(Byval q as String,Byval w as Long,Byval e as Long) as Long
Private Declare Function pl Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4Play"(Byval q as Long,Byval w as Long,Byval e as Long,Byval r as Long,Byval t as Long) as Long
Private Declare Sub st Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4SetMasterTempo"(Byval q as double)
Private Declare Sub cl Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias "GGS4ClearList"()
Private Declare Sub co Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias "GGS4CloseDevice"()
Sub main()
dim w as double
e=op(-1100,0)   'この行は書き換え禁止!
e=al("ddd.mid",0,0)    '""の中は任意のMIDIファイル名
e=pl(0,0,0,0,0)
for w=1to 3step.5
Call st(w)
Application.Wait((Timer+3)/86400)    'EXCELのWAITの引数の単位は丸1日を1とする小数
next
call cl:co:End
End Sub

うなるような音がするだけで、演奏しない。そこで一か八かでdouble型を4バイト型小数変数すなわちsingle型に変えてみると、なんと成功した!

Private Declare Function op Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4OpenDevice"(Byval q as Long,Byval w as Long) as Long
Private Declare Function al Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4AddListFromFileA"(Byval q as String,Byval w as Long,Byval e as Long) as Long
Private Declare Function pl Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4Play"(Byval q as Long,Byval w as Long,Byval e as Long,Byval r as Long,Byval t as Long) as Long
Private Declare Sub st Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4SetMasterTempo"(Byval q as single)
Private Declare Sub cl Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4ClearList"()
Private Declare Sub co Lib"c:\gurugurusmf\gurugurusmf4.dll"Alias"GGS4CloseDevice"()
Sub main()
dim w as single
e=op(-1100,0)   'この行は書き換え禁止!
e=al("ddd.mid",0,0)    '""の中は任意のMIDIファイル名
e=pl(0,0,0,0,0)
for w=1to 3step.5
Call st(w)
Application.Wait((Timer+2)/86400)
next
call cl:co:End
End Sub

 次はこれを十進BASICに移植しようと思い、以下のコオドを実行する。
e=op(-1100,0)
e=al("ddd.mid",0,0)
e=pl(0,0,0,0,0)
for w=1to 3step.5
Call st(w)
Wait DELAY 2
next
call cl
CALL co
End
EXTERNAL Function op(Q,W)
ASSIGN"c:\gurugurusmf\gurugurusmf4.dll","GGS4OpenDevice"
END FUNDTION
EXTERNAL FUNCTION al(q$,w,e)
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4AddListFromFileA"
END FUNCTION
EXTERNAL FUNCTION pl(q,w,e,r,t)
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4Play"
END FUNCTION
EXTERNAL SUB st(q)
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4SetMasterTempo"
END SUB
EXTERNAL SUB ci
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4ClearList"
END SUB
EXTERNAL SUB cl
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4CloseDevice"
END SUB

ヘルプによると以下のようになっているのでもちろんダメ。
数値型の引数は上位桁を切り捨てた32ビット整数として評価して渡す。

そこで、GGS4SetMasterTempo関数に4バイト小数の引数を渡すため、EXCEL VBAに戻り、簡単な4バイト小数のイメエジハイルを作ってみる。

sub q()
dim w as single
open"f:\tenpuhairu\dum.dat"for binary as 1
for w=1to 3step.5
put #1,,w
next
close #1
end sub

これをバイナリエディッタで参照すれば、間違いなく4バイトのデエタが5個書かれているのが解る。
 これを十進Bで読み込んで演奏するためのプログラムを作る。小数イメエジを文字列変数として読み込む。

OPTION CHARACTER byte
DECLARE EXTERNAL FUNCTION op,al,pl
open #1:NAME"f:\tenpuhairu\dum.dat",ACCESS INPUT
INPUT #1:I$
CLOSE #1
LET q=op(-1100,0)
LET q=al("DDD.mid",0,2)
LET q=pl(0,2,0,0,0)
FOR W=0TO 4
LET q$=I$(4*W+1:4*W+4)
CALL ST(q$)
WAIT DELAY 1
q$=""
NEXT
CALL ci
CALL cl
END
EXTERNAL FUNCTION op(q,w)
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4OpenDevice"
END FUNCTION
EXTERNAL FUNCTION al(q$,w,e)
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4AddListFromFileA"
END FUNCTION
EXTERNAL FUNCTION pl(q,w,e,r,t)
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4Play"
END FUNCTION
EXTERNAL SUB ST(q$)
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4SetMasterTempo"
END SUB
EXTERNAL SUB ci
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4ClearList"
END SUB
EXTERNAL SUB cl
assign"c:\gurugurusmf\gurugurusmf4.dll","GGS4CloseDevice"
END SUB

矢張り結果は同じだった。もう十進Bの組み込み関数、PACKDBL$を用いて4バイト小数イメエジをを8バイト小数に換えたりしても全くダメ(ソース略)(もっとも十進Bは、普通のDLL関数の整数型変数を当てるべきところに文字列型変数を当てること自体受け付けないのだが...)。
 すなわち、MSO VBA では4バイト小数型変数が使えるが、十進Bでは使えない、という結論に終わった(もっともVBAのほうも、GGSの使用はどうにかこうにかというレベルなのだが)。
 しかしこれは、十進Bのバグと言うより、十進Bタイプの言語の限界なのだろう。
 十進Bが、JIS FULL BASICの機能を実現するために作られたもので、JISに準拠しなければならないんだから仕方ないんですよね、白石先生。
 

Re: 十進BASICの限界

 投稿者:しばっち  投稿日:2019年12月25日(水)18時27分29秒
  > No.4752[元記事へ]

名無しさんさんへのお返事です。

>  問題のGGS4SETMASTERTEMPO関数は以下の仕様となっている。
>
> void GGS4SetMasterTempo(float tempo);  // C


GGS4SetMasterTempoの呼び出し方が十進BASICでは対応していません。
int型なら簡単に呼び出せたのですが、float型やdouble型を十進BASICから
呼び出すには 下記のようにdoubleやfloatに"*"が必要です。
それからfloat型やdouble型のバイナリーイメージが必要です。

extern "C" __declspec(dllexport) void test(float *a)


もし、ソースをお持ちなら修正してコンパイルし直せば呼び出せるようにはなるかもしれませんが
dllの呼び出し方については、それが十進BASICでの仕様ということで仕方ないかと思います。

十進BASICには2進モード、複素数モード、1000桁モードといった動作モードで
int型やfloat型、double型といった数値型がないためだろうと思われます。

逆に言えば、キャスト(型変換)の問題からは解放されるといえるでしょう。
 

Re: 十進BASICの限界

 投稿者:SHIRAISHI Kazuo  投稿日:2019年12月26日(木)09時34分34秒
  > No.4752[元記事へ]

stdcall の仕様に倣って作成されたDLLであれば,おそらく単精度数も整数と同様にスタックにプッシュしてから呼び出されるので,単精度数のイメージを整数に偽装して渡せば使えると思います。
単精度数のフォーマットは,たとえば,
https://ja.wikipedia.org/wiki/%E5%8D%98%E7%B2%BE%E5%BA%A6%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0
に載っています。
目的の小数を1.*******×2^nの形に書いて,0.*******を2^23倍した数に(n+127)×2^23を足す,みたいな方法で作れるはずです。

http://hp.vector.co.jp/authors/VA008683/

 

Re: 十進BASICの限界

 投稿者:しばっち  投稿日:2019年12月26日(木)20時47分31秒
  > No.4754[元記事へ]

SHIRAISHI Kazuoさんへのお返事です。

> stdcall の仕様に倣って作成されたDLLであれば,おそらく単精度数も整数と同様にスタックにプッシュしてから呼び出されるので,単精度数のイメージを整数に偽装して渡せば使えると思います。
> 単精度数のフォーマットは,たとえば,
> https://ja.wikipedia.org/wiki/%E5%8D%98%E7%B2%BE%E5%BA%A6%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0
> に載っています。
> 目的の小数を1.*******×2^nの形に書いて,0.*******を2^23倍した数に(n+127)×2^23を足す,みたいな方法で作れるはずです。


おもしろそうなのでC++で試してみました。cdecl呼び出しですがうまくいきました。
float型バイナリーイメージをint型に代入してreturnで値を受け取っています。(float2int.cpp)

テストプログラムを作成しfloat型の呼び出しに整数値を偽装して代入しています。(floattest.cpp)
偽装して代入した整数値(変数 N)が元の値(変数 X)と一致しています。


FOR X=0 TO 1 STEP 1/8
   LET N=FLOAT2INT(STR$(X))
   PRINT N;TEST(N);X
NEXT X
END

EXTERNAL  FUNCTION FLOAT2INT(A$)
OPTION CHARACTER BYTE
ASSIGN ".\DLL\float2int.dll","float2int"
END FUNCTION

EXTERNAL  FUNCTION TEST(N)
ASSIGN ".\DLL\floattest.dll","test",FPU
END FUNCTION
------------------------------------------------------------------------
                        float2int.cpp


#include <cstdlib>
using namespace std;

extern "C" __declspec(dllexport) int float2int(char *a)
{
    int n;
    union {
        float f;
        unsigned char c[4];
    } image;

    image.f=atof(a);
    n=image.c[0]+image.c[1]*256+image.c[2]*65536+image.c[3]*16777216;
    return n;
}

------------------------------------------------------------------------
                       floattest.cpp

extern "C" __declspec(dllexport) double test(float a)
{
    return (double)a;
}


                             実行結果

0  0  0
1040187392  .125  .125
1048576000  .25  .25
1052770304  .375  .375
1056964608  .5  .5
1059061760  .625  .625
1061158912  .75  .75
1063256064  .875  .875
1065353216  1  1
 

戻る