WindowsAPIクラスの修正
『大村あつしのExcel VBA Win64/32 API プログラミング』
二年半程前に購入したものの、「だめだ、今の私では歯が立たない……」と諦めていた
コチラの本、『大村あつしのExcel VBA Win64/32 API プログラミング』。
「今ならそこそこ理解できるのではないか」と思い、現在再挑戦中。
で、次のような記述に出くわした。
77ページ
それでは、実際にAPI関数が「2,147,483,648」というLong型がサポートする値を超える数値を返した場合、Long型で宣言された引数は、この数値をどう受け入れるのでしょうか。VBAの環境内では、「2,147,483,648」をLong型変数に代入すればオーバーフローエラーが発生しますが、このケースではエラーは起きません。答えは、Long型引数は「2,147,483,648」という数値を「-2,147,483,647」という数値で受け入れる、です。
79ページ
それでは、実際にAPI関数が「2,147,483,647」を超える数値を返してしまったらどう対処すべきでしょう。この場合、最上位ビットに7は必ず「1」が立っていますから、VBAはその戻り値を「負」と判断してしまいます。つまり、「正」の数値の戻り値を受け取ったLong型変数が「負」の値を示したら、その「負」の値に「4,294,967,296(2の32乗)」を加算して、自ら「正」の数値に反転させてあげれば良いのです。
このロジックをプロシージャに組み込めば、VBAでも「符号なし32ビット長整数」を処理できるようになります。
なんというか、私は今、モーレツに感動している!
情報科学のいろはの「い」すら学んでいない素人ゆえ、そんなこと考えたこともなかったよ!
だから、VBAのLong
型というのは、32ビットとは言いながら、符号あり、ということは最上位ビットが正負の符号を表すために使われるから、実際には正の数の最大値は
0111 1111 1111 1111 1111 1111 1111 1111
、
つまり、10進数で
2,147,483,647
であり、こいつに「1」を足すと、
1000 0000 0000 0000 0000 0000 0000 0000
になってしまうので、10進数にすると、
2,147,483,648
ではなく、
-2,147,483,647
になってしまう。(符号つき長整数の場合、
1000 0000 0000 0000 0000 0000 0000 0000
が最小値なので、10進数にすると-0
ではなく、-2,147,483,647
になる。)
うおおおお! 面白れえ!
しかし待てよ。「反転」させたとして、その数は「2,147,483,647
」を超えているわけだから、もはやLong
型では扱えないよな……。
サンプルコードを見てみる
というわけで、上掲書のサンプルコードを見てみる。
Windowsが起動してからの時間を計測するプロシージャが掲載されていた。
265-266ページ
Windowsを起動してから経過した時間を計測する
前述のGetTickCount関数は、実はWindowsを起動してから経過した時間をミリ秒単位で返すものです。つまり、次のように使えば、Windowsを起動してから経過した時間を計測することが可能です。
リスト3 Module1 Windowsを起動してから経過した時間を計測する(プロシージャ)
Sub GetTickCount_Sample2() Dim lngTimer As LongPtr Dim vntTimer As Variant Dim rc As Long Dim rc2 As Double 'Windowsを起動してから経過した時間を取得' lngTimer = GetTickCount() vntTimer = CDec(lngTimer) If vntTimer < 0 Then vntTimer = vntTimer + 2 ^ 32 End If vntTimer = vntTimer / 1000 / 60 vntTimer = Int(vntTimer) MsgBox "Windowsを起動してから経過した時間: " & _ Format(vntTimer, "#,###分") End Sub
なぜかハンガリアンだし、変数rc
とかrc2
が何のために存在するのかわからないけれど、なるほど、Variant
で受ければ良いらしい。
というわけで、
このときのWindowsAPI
クラスのコードを修正しよう。
コードの修正
修正前のコードはコチラ。
修正後のコードを載せておく。
リスト1 クラスモジュール
Private Declare Function GetTickCount Lib "kernel32" () As Long Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Public Function callGetTickCount() As Long Dim ret As Variant ret = GetTickCount() ret = CDec(ret) If ret < 0 Then ret = ret + 2 ^ 32 callGetTickCount = ret End Function Public Sub callSleep(ByVal milliSeconds As Long) Call Sleep(milliSeconds) End Sub Public Sub waitFor(ByVal milliSeconds As Long) Dim startTime As Long startTime = callGetTickCount() Dim endTime As Long Do Sleep (1) DoEvents endTime = callGetTickCount() Loop Until endTime - startTime > milliSeconds End Sub
これで、callGetTickCount
メソッドが最大「49.7日間」(上掲書より)もの長時間に対応できるようになった。
おわりに
まあ、元の「約24.9日」でさえ使い切ることはまれだろうけれど。
でも、コンピュータに関する基本的なことをもっと勉強した方がいいのかも知れないなあ。
今さらながら基本情報でもちょっと勉強してみようかしら。