[リストへもどる]   [VBレスキュー(花ちゃん)]
一括表示

投稿時間:2005/05/13(Fri) 14:11
投稿者名:フレ
URL :
タイトル:
メソッドの戻り値を配列にするとメモリリークする?
こんにちは。フレと申します。

クラスモジュールの Public メソッドの
戻り値を配列にするとメモリ使用量が
どんどん増えていくという事象が発生しています。

【環境】
OS  : Windows2000 SP4
IDE : VB6 SP6


【質問】
Project1 という名前で 標準 EXE の
プロジェクトを作成し、
Form1 という名前でフォームモジュールを作成しました。

また、Project2 という名前の ActiveX DLL プロジェクトを作成し、
Class1(Instancing:6-GlobalMultiUse) という名前の
クラスモジュールを作成しました。

Project2.Class1 には
以下のメソッドを実装しました。

<Project2.Class1 に実装したメソッド>
Public Function getByteArray() As Byte()
    Dim bytArray(0 To 100) As Byte
    getByteArray = bytArray
End Function


ここまで準備して、
Project1.From1 にボタンを設け、
ボタンクリックイベントで
Project2.Class1 の getByteArray() を
コールすると、メモリが開放されずに
どんどんメモリ使用量が増えていきます。

Private Sub Command1_Click()
    Dim i As Long
    For i = 1 To CLng(500000)
        Call Project2.getByteArray
    Next i
End Sub

なお、Project2.Class1 の getByteArray() を
Project1 の標準モジュールの関数として
コピペしてコールした場合は
メモリ使用量は蓄積されないようです。

標準 Exe 以外の ActiveX DLL などのメソッドでは
戻り値を配列にしてはいけないなどの
ルールがあるのでしょうか?
※VB歴1年程度ですが、このような話を
 聞いたことがなかったので(^^;)

戻り値で配列を返さずに
ByRef の引数で配列を受け取って
処理を行うように変更することで
本問題を解決できることはわかりましたが、
既に作成してしまっているメソッドについては
簡単にメソッドのシグネチャを変更できない状況で、
どうしようか悩んでいます。

関連する情報などご存知の方がいらっしゃったら
教えてください。

以上です。
よろしくお願いします。

投稿時間:2005/05/13(Fri) 14:41
投稿者名:フレ
URL :
タイトル:
Re: メソッドの戻り値を配列にするとメモリリークする?
お世話になっています。
フレです。

メモリ使用量が増える事象の原因は未だわかりませんが、
メソッドのシグネチャを変更しないでも
問題を解決する方法がわかりました。

ボタンクリックイベントの処理を
以下のように修正したら、
事象が再現しなくなりました。

Private Sub Command1_Click()
Dim bytArray() As Byte
Dim i As Long
For i = 1 To CLng(500000)
bytArray = Project2.getByteArray
Next i
End Sub

元々、今回の事象を発見した時は、
モジュールのメソッドの処理時間を
計測する目的で呼び出していたため、
戻り値は受け取る必要がなかったので
Call でメソッドを呼び出していましたが、
これだとまずかったようです。

Project2.getByteArray の戻り値を
呼び出し元側で受け取るように修正すれば
正常に動作するようになりました。

実際の業務アプリでは、Public Function のメソッドの
戻り値を受け取らないような作りになっている個所は
存在しませんので、今回の事象が業務アプリで
再現することはないと判断しました。

以上です。
お騒がせしました。

投稿時間:2005/05/13(Fri) 22:28
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re^2: メソッドの戻り値を配列にするとメモリリークする?
標準EXEプロジェクトでテストしてみました。

============ Form1 ============
Option Explicit

Private Sub Command1_Click()
    Debug.Print "--Start--"
    Proc
    Debug.Print "--End--"
End Sub

Private Sub Proc()
    Dim i As Long, C As Class1
    Dim V As Variant
    Set C = New Class1
    For i = 1 To 5
        Call C.Test    '★パターン1★
        'V = C.Test    '★パターン2★
    Next
    Set C = Nothing
End Sub

Private Sub Form_Terminate()
    Debug.Print "--Terminate--"
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Debug.Print "--Unload--"
End Sub

============ Class1 ============
Option Explicit
Public Function Test() As Class2()
    Dim X(0) As Class2
    Set X(0) = New Class2
    Test = X
    Set X(0) = Nothing
End Function

============ Class2 ============
Option Explicit
Private ID As Long
Private Sub Class_Initialize()
    ID = Int(Rnd() * 1000000)
    Debug.Print "Open: ", ID
End Sub
Private Sub Class_Terminate()
    Debug.Print "Close:", ID
End Sub
--------------------

上記を実行すると、当方では以下のような結果になりました。



★パターン1★
--Start--
Open:          705547
Open:          533424
Open:          579518
Open:          289562
Open:          301948
Close:         301948
--End--
--Unload--
--Terminate--
Close:         289562
Close:         579518
Close:         533424
Close:         705547


★パターン2★
--Start--
Open:          705547
Open:          533424
Close:         705547
Open:          579518
Close:         533424
Open:          289562
Close:         579518
Open:          301948
Close:         289562
Close:         301948
--End--
--Unload--
--Terminate--

投稿時間:2005/05/13(Fri) 22:44
投稿者名:魔界の仮面弁士
Eメール:
URL :
タイトル:
Re^3: メソッドの戻り値を配列にするとメモリリークする?
あぁ、これと同じ状況になるみたいですね。

[UBound または LBound に配列の戻り値を渡すとメモリ リーク]
hhttp://support.microsoft.com/default.aspx?scid=kb;ja;197190


これを受けて、先のサンプルの Call の部分を、UBoundに渡す形式で追試してみたところ、
これも Call と同様、アプリ終了までメモリが解放されないという結果になりました。

    For i = 1 To 5
        n = UBound(C.Test())
    Next

投稿時間:2005/05/17(Tue) 18:41
投稿者名:フレ
URL :
タイトル:
Re^4: メソッドの戻り値を配列にするとメモリリークする?
こんにちは!
フレです。

魔界の仮面弁士さん、いろいろ情報ありがとうございます。
お礼が遅くなってすみませんでした。

> あぁ、これと同じ状況になるみたいですね。
>
> [UBound または LBound に配列の戻り値を渡すとメモリ リーク]
> hhttp://support.microsoft.com/default.aspx?scid=kb;ja;197190
>
>
こういう情報があったんですね。
調査不足でした。

今までVBでの配列を扱いについてあまり意識せずに開発してきましたが、
今後は意識してコーティングするようにしたいと思います。

関数の戻り値を必要なくてもローカル変数に代入する等で
事象を回避できることがわかりましたので助かりました。

以上です。
情報ありがとうございました。