自作WindowsAPIクラスを修正した

WindowsAPIクラスの修正

『大村あつしのExcel VBA Win64/32 API プログラミング』

二年半程前に購入したものの、「だめだ、今の私では歯が立たない……」と諦めていた

f:id:akashi_keirin:20190105203721j:plain

コチラの本、『大村あつしの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ビット長整数」を処理できるようになります。

なんというか、私は今、モーレツに感動している!

情報科学のいろはの「い」すら学んでいない素人ゆえ、そんなこと考えたこともなかったよ!

C言語の長整数は符号なし、VBAの長整数は符号あり……。

だから、VBALong型というのは、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で受ければ良いらしい。

というわけで、

akashi-keirin.hatenablog.com

このときの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日」でさえ使い切ることはまれだろうけれど。

でも、コンピュータに関する基本的なことをもっと勉強した方がいいのかも知れないなあ。

今さらながら基本情報でもちょっと勉強してみようかしら。