tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトルCells.FindNextのループ中の解放
記事No9403
投稿日: 2009/09/16(Wed) 11:49
投稿者camputer

 こんにちは、質問させていただきます。どうぞよろしくお願いいたします。
 プログラム歴役10年+VB歴1年弱で、開発環境はXP+VB2008+Excel2003になります。

 エクセルのセルを検索しております。VBAでは解放の必要がないので出来ておりましたが、
もし.NETで
        Do  
                    Dim myRng As Excel.Range = xlCells.FindNext(myRng)

          MRComObject(myRng)
        Loop
のような事をやってしまうと、次のFindNext(myRng)でmyRngが使えなくなると思います。
そこで下のように一度myFindNextに入れておいて
myRngと交互に解放しようと考えたのですが、

                Dim xlCells As Excel.Range = xlSheet.Cells
                Dim myRng As Excel.Range = xlCells.Find("文字列")

                If Not MyRng Is Nothing Then
                    Dim 最初セル As String = myRng.Address
          Do  
                        Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
                        MRComObject(myRng)
                        myRng = myFindNext
                        MRComObject(myFindNext) '←これを入れると、
                         'myFindNextとmyRngの両方が
                         '使えなくなります。。。
                        MsgBox(myRng.Value)

            If myRng.Address = 最初セル Then
                          MRComObject(myRng) : myRng = Nothing
                        End If
                    Loop Until myRng Is Nothing
                End If
                MRComObject(myRng)
                MRComObject(xlCells)

 MRComObject(myFindNext)とすると、それ以降myFindNextとmyRngの両方が
使えなくなってしまいます。。(エラー「基になる RCW から分割された
COM オブジェクトを使うことはできません。」となります。)
 検索でFindNextを解放しているようなコードを探しましたが、以外に出てこず、
他のやり方が思いつきませんでした。。。
何かアドバイスかヒントをいただけないでしょうか。どうぞよろしくお願いいたします。


 あと、4行目の
  Dim 最初セル As String = myRng.Address 
ですが、エクセルのオブジェクトブラウザからRangeクラスAddressメンバの型を調べると、
本来は
  Dim 最初セル As Excel.XlReferenceStyle = myRng.Address
になるはずだと思うのですが、そうやると
エラー「String "$A$2" から型 'Integer' への変換は無効です。」となります。
そこでAs Stringのままにしている次第でございますが、上級者の方々の経験上、
このような事で何か問題があると考えていらっしゃるかどうかご意見をいただけないでしょうか。

 何かヒントをいただくことができればと思います。
どうぞよろしくお願いいたします。

[ツリー表示へ]
タイトルRe: Cells.FindNextのループ中の解放
記事No9404
投稿日: 2009/09/16(Wed) 12:05
投稿者魔界の仮面弁士
> 1:   Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
> 2:   MRComObject(myRng)
> 3:   myRng = myFindNext
> 4:   MRComObject(myFindNext) '←これを入れると、
>                  'myFindNextとmyRngの両方が
>                  '使えなくなります。。。
> 5:   MsgBox(myRng.Value)

一行ずつ順に見ていくと、使えなくなる理由が分かるかと思います。



myRng に、COMオブジェクト1 がセットされているとすると…

> 1:   Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
→ myFindNext に オブジェクト2 をセット

> 2:   MRComObject(myRng)
→ オブジェクト1を破棄する
  (myRng の参照先が使えなくなる)

> 3:   myRng = myFindNext
→ myRng に オブジェクト2 をセット
  (myRng と myFindNext が、共に同じオブジェクトを参照した状態となる)

> 4:   MRComObject(myFindNext) '←これを入れると、
>                  'myFindNextとmyRngの両方が
>                  '使えなくなります。。。
→ オブジェクト2を破棄する
  (myRng と myFindNext の両方の参照先が使えなくなる)

> 5:   MsgBox(myRng.Value)
→ オブジェクト2は破棄されているので、使えない

という流れで処理される事になります。


破棄するべきは、以前に参照していたオブジェクト1だけであって、
オブジェクト2の方は、次回のループ先頭にある
「Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)」
のタイミングまで保持し続けておかなければなりません。



>   Dim 最初セル As String = myRng.Address 
> ですが、エクセルのオブジェクトブラウザからRangeクラスAddressメンバの型を調べると、
> 本来は
>   Dim 最初セル As Excel.XlReferenceStyle = myRng.Address
> になるはずだと思うのですが、
いえ、違います。

Range.Address のプロパティ定義は、Excel のバージョンによっても異なりますが、
少なくとも Excel 97 以降では、その戻り値は String 型と定められています。


XlReferenceStyle を使うとしても、それは、
 Dim s As String = rng.Address(False, True, Excel.XlReferenceStyle.xlR1C1)
のように、省略可能な引数の一つとして指定するのみです。

[ツリー表示へ]
タイトルRe^2: Cells.FindNextのループ中の解放
記事No9405
投稿日: 2009/09/16(Wed) 14:55
投稿者camputer
 魔界の仮面弁士 様

 どうもありがとうございます!!
>→ オブジェクト2を破棄する
>  (myRng と myFindNext の両方の参照先が使えなくなる)
についてですが、アドバイスいただいた後もしばらく考え続けてみましたが
myFindNextのみオブジェクト2を破棄する、といったような方法が思いつきません。。。
どうにかしてオブジェクト2を次回のループ先頭まで保持しておきながら、
毎回myFindNextを解放処理する必要があると思うのですが。
根本的に検索の文法がおかしいのでしょうか???
 一応下のようにMRComObject(myFindNext)をコメントアウトしてもExcel.EXEが
解放されますが、オブジェクト2を破棄できていないはずですし。。

                Dim xlCells As Excel.Range = xlSheet.Cells
                Dim myRng As Excel.Range = xlCells.Find("文字列")

                If Not MyRng Is Nothing Then
                    Dim 最初セル As String = myRng.Address
          Do  
                        Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
                        MRComObject(myRng)
                        myRng = myFindNext
                            'MRComObject(myFindNext) '←コメントアウト

                        MsgBox(myRng.Value)

            If myRng.Address = 最初セル Then
                          MRComObject(myRng) : myRng = Nothing
                        End If
                    Loop Until myRng Is Nothing
                End If
                MRComObject(myRng)
                MRComObject(xlCells)

 Dim s As String = rng.Address(False, True, Excel.XlReferenceStyle.xlR1C1)
についてはどうもありがとうございました!ご指摘いただけたので
勘違いに気付くことができました^^; As Stringが画面の下に隠れておりました。
As XlReferenceStyle にリンクが付いておりましたので、重ねて勘違いした
次第でございます。
ご指摘いただきどうもありがとうございました。

[ツリー表示へ]
タイトルRe^3: Cells.FindNextのループ中の解放
記事No9406
投稿日: 2009/09/16(Wed) 16:04
投稿者魔界の仮面弁士
> myFindNextのみオブジェクト2を破棄する、といったような方法が思いつきません。。。
変数とオブジェクトインスタンスを分けて考えてください。

一番最初の
>> Dim myRng As Excel.Range = xlCells.FindNext(myRng)
で問題があるのは、何故だかわかりますか? (正確には「myRng = xlCells.FindNext(myRng)」)

FindNext 前は、myRng はオブジェクト1を参照していたけれども、
FindNext 後は、myRng はオブジェクト2を参照するようになりますよね。

このため、オブジェクト1はどの変数からも参照されていない状態に
なってしまったため、MRComObject で解放させる方法が使えなくなってしまい、
解放漏れに繋がってしまうというわけです。


> どうにかしてオブジェクト2を次回のループ先頭まで保持しておきながら、
ループ先頭で保持している変数は何ですか? myRng ですよね。
ですから、myRng にオブジェクト2を“破棄せずに”セットすれば良いのです。

最初の質問では、myRng にオブジェクト2をセットしてはいますが、
その直後にそれが破棄されていました。それが余計な作業だったわけです。


> 'MRComObject(myFindNext) '←コメントアウト
正解です。この破棄作業が余計だったわけですね。


> If myRng.Address = 最初セル Then
>    MRComObject(myRng) : myRng = Nothing
> End If
単にループの脱出が目的なら、Exit Do を呼び出せば済みますよ。
myRng の破棄作業は、既にループ脱出後に用意してありますので、
この段階での myRng の破棄は不要です。

また、Nothing 代入の役割は、すでに MRComObject が担っているかと思います。
(MRComObject をどのように実装したのかもよりますが…)


> 毎回myFindNextを解放処理する必要があると思うのですが。
ループ中では、「FindNext の戻り値から得た Range」を破棄するのではなく、
「FindNext の引数に渡した Range」を破棄していく事になるかと思います。


>  一応下のようにMRComObject(myFindNext)をコメントアウトしてもExcel.EXEが
> 解放されますが、オブジェクト2を破棄できていないはずですし。。
オブジェクト2は、次回のループ時に破棄されます。

表現が難しいですが、この場合には、
 「今回のオブジェクト2」=「次回のオブジェクト1」
であるという事です。

別の書き方をすると、こんな感じですかね。



> Dim myRng As Excel.Range = xlCells.Find("文字列")
  → myRng に オブジェクト1 をセット
> Do
--- ループ 1 回目 ---
>     Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
      → myFindNext に オブジェクト2 をセット
>     MRComObject(myRng)
      → オブジェクト1を破棄
>     myRng = myFindNext
      → myRng に オブジェクト2 をセット
>     MsgBox(myRng.Value)
--- ループ 2 回目 ---
>     Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
      → myFindNext に オブジェクト3 をセット
>     MRComObject(myRng)
      → オブジェクト2を破棄
>     myRng = myFindNext
      → myRng に オブジェクト3 をセット
>     MsgBox(myRng.Value)
--- ループ 3 回目 ---
>     Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
      → myFindNext に オブジェクト4 をセット
>     MRComObject(myRng)
      → オブジェクト3を破棄
>     myRng = myFindNext
      → myRng に オブジェクト4 をセット
>     MsgBox(myRng.Value)
--- 以下繰り返し ---
> Loop Until myRng.Address = 最初セル
--- ループ 脱出 ---
> MRComObject(myRng)
  → オブジェクト4を破棄

[ツリー表示へ]
タイトルRe^4: Cells.FindNextのループ中の解放
記事No9407
投稿日: 2009/09/16(Wed) 16:53
投稿者camputer
 魔界の仮面弁士 様

 どうもありがとうございます!!
本当にいつも沢山のアドバイスをいただき光栄でございます。

 これだけのアドバイスをいただけたので、自分で解決できるはずです。


 私が今まで誤解していたと思われる箇所が、今回ご説明いただけた
>変数とオブジェクトインスタンスを分けて考えてください。

>オブジェクト2は、次回のループ時に破棄されます。
の部分です。
変数(myRngとmyFindNext)をともに毎回MRComObjectする方法ばかり考えておりました。
どうもありがとうございます。

 MRComObjectにつきましては、ずっとVBレスキュー花ちゃんで御提示いただいていた
コードを使わせていただいておりますが、
(これも魔界の仮面弁士様がご作成くださったコードのようですね^^ 
花ちゃん様のコメントを読ませていただいていて気付きました。
重ね重ねどうもありがとうございますm(_ _)m   )
もし次のコード@のような書き方をするのであれば、ObjにはNothingを入れない方が
良いのかな(?)などと考えまして、ためしにSub MRComObject内の最後の
「objCom = Nothing」をコメントアウトしてみて、コードAのように書いております。

【コード@】
 For Each Obj in Objs
       :
       :
      MRComObject(Obj) '←ここでObjにNothingを入れない方がよいのかな?と考えました。
  Loop
  MRComObject(Objs)

  
【コードA】  
 For Each Obj in Objs
       :
       :
      MRComObject(Obj) '(Obj=Nothingを含まず)
  Loop
  MRComObject(Objs) : objs = Nothing


以下、http://hanatyan.sakura.ne.jp/dotnet/Excel01.htmからの引用です。

    Public Shared Sub MRComObject(Of T As Class)(ByRef objCom As T, Optional ByVal force As Boolean = False)
        If objCom Is Nothing Then
            Return
        End If
        Try
            If System.Runtime.InteropServices.Marshal.IsComObject(objCom) Then
                If force Then
                    System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objCom)
                Else
                    Dim count As Integer = System.Runtime.InteropServices.Marshal.ReleaseComObject(objCom)
                    Debug.WriteLine(count)
                End If
            End If
        Finally
            'objCom = Nothing '←ここをコメントアウトして試しております。
                         間違っているかもしれませんが。。。
        End Try
    End Sub

 取り急ぎ御礼だけになってしまいますが、自分で解決してまたご報告させていただきます。
どうもありがとうございました!

[ツリー表示へ]
タイトルRe^5: Cells.FindNextのループ中の解放
記事No9408
投稿日: 2009/09/16(Wed) 17:46
投稿者魔界の仮面弁士
>  MRComObjectにつきましては、ずっとVBレスキュー花ちゃんで御提示いただいていた
> コードを使わせていただいておりますが、
実のところ、MRComObject は投稿時期によって、複数の実装があったりします。


> (これも魔界の仮面弁士様がご作成くださったコードのようですね^^ 
> 以下、http://hanatyan.sakura.ne.jp/dotnet/Excel01.htmからの引用です。
確かに、そのコードは私が手を加えた物ではあるのですが、そもそも
『Sub MRComObject』の初出は、花ちゃんさん作の物であったかと思います。

# そしてその元ネタは、恐らく KB317109 の『Sub NAR』であろうと推測。。。
# http://support.microsoft.com/kb/317109/ja


> MRComObject(Obj) '←ここでObjにNothingを入れない方がよいのかな?と考えました。
入れても良いですし、入れなくても構いません。
(その Obj が WithEvents 付き変数であるならば、入れない方が良いでしょう)


COM オブジェクトの解放と破棄自体は、Marshal.ReleaseComObject の
呼び出しだけで充分です。とはいえ、厳密にはそれだけでは完全なメモリ解放とは
なりませんし、また、Nothing 代入すること自体が無意味というわけでもありません。


このあたりは厄介な事情が絡んでくるので、簡単に説明するのは難しいです。
少し難しい話になりますが、興味があれば下記を参照してみてください。
http://hpcgi1.nifty.com/MADIA/vbnet/wwwlng.cgi?print+200908/09080001.txt
http://msdn.microsoft.com/ja-jp/library/aa159887%28office.11%29.aspx#odc_ofcomplexcomobjectspias_managingcomobjectswithappdomains
http://shinichiaoyagi.blog25.fc2.com/blog-entry-180.html

[ツリー表示へ]
タイトルRe^6: Cells.FindNextのループ中の解放
記事No9409
投稿日: 2009/09/16(Wed) 19:37
投稿者camputer

 魔界の仮面弁士 様

>『Sub MRComObject』の初出は、花ちゃんさん作の物であったかと思います。
なんと合作でございましたか.ではなおさらm(_ _)m 
どうもありがとうございます>花ちゃん様,魔界の仮面弁士様

>(その Obj が WithEvents 付き変数であるならば、入れない方が良いでしょう)
については前に魔界の仮面弁士様から教えていただきました.
以前こちらで質問させていただいた時の
「Re^7: PowerPointのオブジェクト開放ができません」で...ですね^^↓
>なお、WithEvents 変数は、ここのサイトで紹介されている MRComObject の実装だと
>正しく処理できない可能性があるので、イベントを使う場合には注意が必要かも。


http://hpcgi1.nifty.com/MADIA/vbnet/wwwlng.cgi?print+200908/09080001.txt
についても読んだことがあります....
「Re^5: VB2008で、単一のExcel.Shapesオブジェクトの取得方法」で魔界の仮面弁士様が
リンクを付けてくださいました.他のはまだ読んだことが無さそうなので,
一通り読んで理解できるところから理解していきます.

 ご親切にどうもありがとうございます!おかげでまた色々と理解が深まります.
コーディング完了したらまたご報告させていただきます!!

[ツリー表示へ]
タイトルRe^7: Cells.FindNextのループ中の解放
記事No9410
投稿日: 2009/09/16(Wed) 22:13
投稿者camputer

 魔界の仮面弁士 様

 どうもありがとうございました!!
あの後,下のようにコードを書き換えせさていただきました.
念のため,myRngとmyFindNextが偶然解放されていないかどうかをテストしてみたかったので
これらを多用するサブルーチンをLoopの中で呼び出してみましたが,
きれいに解放出来ているようです.本当にどうもありがとうございます!!

                If Not myRng Is Nothing Then
                    Dim 最初セル As String = myRng.Address
                    Do

                        'ここで300行程度のサブルーチンを呼び出してみましたが,
            '問題なさそうでございます

                        Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
                        MRComObject(myRng)
                        myRng = myFindNext
                        If myRng Is Nothing Then Exit Do
                    Loop Until myRng.Address = 最初セル
                End If

 数時間前まで(魔界の仮面弁士様からアドバイスいただく前)の私には,
上のコードでは「myFindNextが解放されていない」ように見えて
                     仕方がありませんでした^^;
長い間,オブジェクトは変数単位で解放する必要があるものだと...orz
 アドバイスいただけましたおかげで,また理解が深まりましたし,
ようやくコードの続きを考えて楽しむことができます.
関連して勉強することも増えてよかったです.
 いつも言葉でしかお礼をさせていただく事しかできませんが,
沢山のアドバイスをいただき,本当にどうもありがとうございます!!
今後もどうぞよろしくお願いいたします.  Camputer.

[ツリー表示へ]
タイトルRe^8: Cells.FindNextのループ中の解放
記事No9411
投稿日: 2009/09/16(Wed) 23:51
投稿者魔界の仮面弁士
> きれいに解放出来ているようです.
あれ? 提示されたコードだと、

>                 If Not myRng Is Nothing Then
>                     Dim 最初セル As String = myRng.Address
>                     Do
>
>                         'ここで300行程度のサブルーチンを呼び出してみましたが,
>             '問題なさそうでございます
>
>                         Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
>                         MRComObject(myRng)
>                         myRng = myFindNext
>                         If myRng Is Nothing Then Exit Do
>                     Loop Until myRng.Address = 最初セル
                      MRComObject(myRng)  '★
>                 End If
>

の★に相当する処理がループ後に行われないと、解放されないのではありませんか?

『Loop Until myRng.Address = 最初セル』という書き方をしているという事は、
このループを抜けた時点で、まだ myRng は生きているという事になりますので、
このオブジェクトもまた、解放対象になりえるかと思います。


もし、Loop 後に ★に相当する処理が無くても解放されるのだとすれば、それは
ループを『If myRng Is Nothing Then Exit Do』で脱出している場合でしょうね。

# ただし、そこの条件に到達するためには、xlCells.FindNext が Nothing を返す
# 必要があります。そのためには、ループ前の xlCells.Find("検索文字列") の
# 検索処理がヒットせず、Nothing を返す場合ぐらいしか思い当たらないのですが…。

[ツリー表示へ]
タイトルRe^9: Cells.FindNextのループ中の解放
記事No9412
投稿日: 2009/09/17(Thu) 09:18
投稿者camputer

 魔界の仮面弁士 様

 どうもありがとうございます!
 どうもすみません。ループ内にばかり気を取られて、離れた場所にある
MRComObject(myRng) をコピペしておりませんでした。。。
でも下のようにEndIfの外に出しておりましたが、これでは一番初めの
myRng Is Nothingの時にもMRComObjectを通すことになるので良くないですね。。。
ご指摘いただいてから気付きましたm(_ _)m

>                 If Not myRng Is Nothing Then
>                     Dim 最初セル As String = myRng.Address
>                     Do
>
>
>
>                         Dim myFindNext As Excel.Range = xlCells.FindNext(myRng)
>                         MRComObject(myRng)
>                         myRng = myFindNext
>                         If myRng Is Nothing Then Exit Do
>                     Loop Until myRng.Address = 最初セル
                  MRComObject(myRng)  '←ご指摘いただいてから修正しました。
>                 End If
>           :
>           :
                  MRComObject(myRng)  '←ここに入れておりました。

 膨大なアドバイスをいただきどうもありがとうございます!
(私の理解とコーディングが遅いせいかも知れませんが・・・orz)

 私がずっと扱っておりましたFortranでのコーディング時には、考えもしなかったような
概念ばかりなので悪戦苦闘しておりますが、ご親切にアドバイスいただけるおかげで
なんとなくですが、VBで出来る事のイメージングも出来始めたので、
コーディングのアイディアを練るのが楽しみです。
(まだ8割以上が検索・調査時間ですけど。。。^^;)

ご指摘どうもありがとうございました!!今後もどうぞよろしくお願いいたします!
                               Camputer.

[ツリー表示へ]