質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.77%

VBAメモリーリークについて

解決済

回答 2

投稿

  • 評価 0
  • クリップ 3
  • VIEW 39K+

score 13

 VBAについて教えてください。
以下の様に、Loopの時間間隔を取得するプログラムを作成しています
lngTimer1 = GetTickCount
・・・・処理
Dim iTimer4 As Integer
lngTimer2 = GetTickCount
 lngTimer3 = lngTimer2 - lngTimer1       
 iTimer4 = CInt(lngTimer3)
 DoEvents
 Worksheets("Sheet1").OLEObjects("LblTackt").Object.Caption = CStr(iTimer4)

この際、Worksheetsオブジェクトが実行されるたびにタスクマネージャのメモリが大きくなっていきます。
書き方でおかしいところはありますか?

  • クリップを取り消します

  • 質問の評価を上げたことを取り消します

  • 質問の評価を下げたことを取り消します

回答 2

checkベストアンサー

+3

VBAのメモリー管理は 参照カウンタ方式 です。仕組みはシンプルですが、漏れ無く開放 するにはそれなりの注意が必要です。

まずは、自分が以前に同様の問題の解決に役立ったページをご連携致します。実際にはかなり膨大な数のページを参照しましたが、とりあえず主だったものを3つだけ…

4.1.5 オブジェクト消滅の管理
Excelのプロセスが正常に終了しない理由(その1)
15.消えないExcelのプロセス

Excel VBA以外の話題も含まれていますが、原理は同じです。ちょっと長いのですが、辛抱して注意深くお読みください。

要点を簡単にまとめると、

  • メモリーリークが発生するのは参照カウンタが0になっていないために開放されないオブジェクトがあるから
  • 使用済みオブジェクトを確実に開放するには使用済みオブジェクトの参照カウンタを確実にデクリメントする
  • 「暗黙の参照」が発生しないように気をつける

このうち、一番面倒なのが、最後の『暗黙の参照を発生させない』という点です!

これは口で言うのは簡単なのですが実践するとなると結構面倒です。
自分はまだExcelのオブジェクトモデルの全容を把握出来ていないので、どこまでやれば必要十分確実なのかが分からないのですが、以下のようなコーディング方法はオブジェクトの開放漏れを発生させてしまうようです。

  • オブジェクトを省略してプロパティを指定する
  • 参照する個々のオブジェクトを逐一変数で受けずに使用する

2番目の『逐一変数で受ける』というのは分かりにくいですが、平たく言えば .(ピリオド)が2つ以上繋がるようなコーディング方法は暗黙の参照を引き起こし、オブジェクトの開放漏れに繋がる可能性があるということです。

ですから、(冗長な部分もあるかもしれませんが)参照カウンタを確実にデクリメントしてオブジェクトの開放漏れを防ぐには、以下のようなコーディング方法が必要ということになります。

(修正前)
Worksheets("Sheet1").OLEObjects("LblTackt").Object.Caption = CStr(iTimer4) 

(修正後)
Dim xlApp As Excel.Application
Dim xlBook As Worksheet
Dim objOle As Object
Dim objTarget As Object

Set xlApp = GetObject(, "Excel.Application")
Set xlBook = xlApp.Worksheets("Sheet1")
Set objOle = xlBook.OLEObjects("LblTackt")
Set objTarget = objOle.Object

objTarget.Caption = CStr(iTimer4) 

Set objTarget = Nothing
Set objOle = Nothing
Set xlBook = Notihng
Set xlApp = Nothing

Excel VBA は気軽に手間なく実装出来るところが最大の魅力でもあるので、いわゆる手抜きをしたコーディング方法でも適宜暗黙の参照を適切に扱い実行可能にしてくれます。
扱うオブジェクトのサイズが小さかったり、ループ回数が少ない場合には、メモリーの枯渇が顕在化する前に作業が完了してExcelのブックを閉じてしまうので、細かいことを気にしなくても全く問題ありません。

しかし、

  • 巨大なオブジェクトを扱う
  • ループの回数が多い
  • 長時間プログラムを実行し続ける

などの場合には、参照カウンタのデクリメントを意識しながら使用の終わったオブジェクトを確実に開放しないと、メモリーリソースがすぐに枯渇して実行時エラーが発生してしまいます。

ですので、必ずしも全てを上記のような面倒なコーディング方法にしなくても良いと思いますが、

  • サイズの大きなオブジェクトを扱う場合
  • 繰り返し回数の多いループの中でオブジェクトを扱う場合

については、参照カウンタを明示的にデクリメントできるような実装方法を心がけてみてください。

以上、幾らかでもご参考になれば幸いです。

投稿

pi-chan

score 5782

  • ExcelVBAer

    ExcelVBAer

    2015/12/24 00:58

    勉強させて頂きました。

+1

とりあえず、以下で回避できませんか?

Dim obj As OLEObject

' 中略

obj = Worksheets("Sheet1").OLEObjects("LblTackt")
obj.Object.Caption = CStr(iTimer4) 

' 中略

' obj が要らなくなったら
Set obj = Nothing

これで解決したなら、「VBA OLEオブジェクト 解放」あたりで調べてみることをお勧めします。

投稿

編集

kozuchi

score 1170

  • ExcelVBAer

    ExcelVBAer

    2015/12/21 15:15

    最初と最後のコードは、
    Set obj = ○○
    と頭に[Set]が必要ですよね?
  • kozuchi

    kozuchi

    2015/12/21 15:45

    おっしゃる通りです、ご指摘ありがとうございます。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る