tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトルPowerPointのオブジェクト開放ができません
記事No9291
投稿日: 2009/08/26(Wed) 19:01
投稿者camputer
 こんばんは。質問させていただきます。
10年近く研究で計算プログラムを作成しておりましたが、1年ほど前にVB.NETに触れました。
camputerと申します。どうぞよろしくお願いいたします。

 開発環境はVB.NET+XP+PPT2003になります。
PPTで

 For Each Slide In Slides
      For Each Shape In Shapes
            
      Next
  Next

ということをやりたいのですが、これら行のオブジェクト開放ができず、長い間悩んでおります。
今までオブジェクト開放については、ほとんど花ちゃんで勉強させていただきましたので,
何か修正のヒントをいただければと思い投稿させていただきました。よろしくお願いいたします。
 以下,作成したコードを載せさせていただきます.
  
        Dim myAPP As PowerPoint.Application
        myAPP = CreateObject("PowerPoint.Application")
        Dim myPre As PowerPoint.Presentation
        Dim myPres As PowerPoint.Presentations = myAPP.Presentations
        Dim mySlide As PowerPoint.Slide
        Dim mySlides As PowerPoint.Slides
        Dim myShape As PowerPoint.Shape
        Dim myShapes As PowerPoint.Shapes
  
            myPre = myPres.Open("C:\test\test.ppt", WithWindow:=MsoTriState.msoFalse)
            mySlides = myPre.Slides
  
            For Each mySlide In mySlides '←このFor〜Next文を無くせばPOWERPNT.EXEが消えます。
                MRComObject(mySlide) : mySlide = Nothing
            Next
  
            MRComObject(mySlides) : mySlides = Nothing
            myPre.Save() : myPre.Close()
            MRComObject(myPre) : myPre = Nothing
        MRComObject(myPres) : myPres = Nothing
        myAPP.Quit() : MRComObject(myAPP) : myAPP = Nothing
  
  
  
 色々と試しておりますと、
Dim mySlide As PowerPoint.Slideを消して、
For文の直前でDim mySlide As Object
としてみると、たまにですが開放できるようになったのですが、
同様の方法でFor Each myShape In myShapesを加えてみたところ、
こちらも開放できませんでした。。。↓
              
          
  
       Dim myAPP As PowerPoint.Application
        myAPP = CreateObject("PowerPoint.Application")
        Dim myPre As PowerPoint.Presentation
        Dim myPres As PowerPoint.Presentations = myAPP.Presentations
        Dim mySlides As PowerPoint.Slides
        Dim myShapes As PowerPoint.Shapes
  
            myPre = myPres.Open(Mypath & FName, WithWindow:=MsoTriState.msoFalse)
            mySlides = myPre.Slides
            Dim mySlide As Object        '←この宣言方法にすると,
            For Each mySlide In mySlides '←このFor文の部分は解放されることが多くなったのですが...
                myShapes = mySlide.Shapes
                Dim myShape As Object
  
                For Each myShape In myShapes '←こちらが開放されません。このFor〜Next文を無くせばPOWERPNT.EXEが消えます。
                    MRComObject(myShape) : myShape = Nothing
                Next
                MRComObject(myShape) : myShape = Nothing
  
                MRComObject(myShapes) : myShapes = Nothing
                MRComObject(mySlide) : mySlide = Nothing
            Next
            MRComObject(mySlides) : mySlides = Nothing
            myPre.Save() : myPre.Close()
            MRComObject(myPre) : myPre = Nothing
        MRComObject(myPres) : myPres = Nothing
        myAPP.Quit() : MRComObject(myAPP) : myAPP = Nothing
  
  
MRComObjectについては、ずっと
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
  
  
  3ヶ月以上前にhttp://virus.okwave.jp/qa5002663.htmlで質問させていただいたのですが、
MRComObjectの位置を変えてみてもうまく開放できず、しばらくこの件で悩んでおります。
(3ヶ月前にクローズしている質問なので重複質問にならないと思うのですが。。。。。)
 それと,たまにうまく開放される時があるのですが、POWERPNT.EXEやEXCEL.EXEの
開放というのは、同じコードであれば、PCの状態や種類にかかわらず開放される、
というものではないのでしょうか???
 またコードの他の書き方についても、もし何かつっこんでいただけるところがございましたら、
勉強のきっかけにさせていただければと思いますのでアドバイスをいただけないでしょうか。
 何卒よろしくお願いいたします。

[ツリー表示へ]
タイトルRe: PowerPointのオブジェクト開放ができません
記事No9292
投稿日: 2009/08/26(Wed) 22:28
投稿者Hongliang
列挙時は少々ややこしいことになりますね。
For Each は、コンパイル時に GetEnumerator の呼び出しに変換されます。
例えば

For Each mySlide In mySlides
Next

というソースは、大雑把に言って

Dim enumerator As IEnumerator = mySlides.GetEnumerator()
While enumerator.MoveNext()
    Dim mySlide As Object = enumerator.Current
End While

というコードになります。IEnumerator は列挙子と呼ばれます。
このとき、ReleaseComObject で解放する必要がある参照は、
・mySlides が 1 つ
・mySlide が列挙されるごとにそれぞれ 1 つずつ
持っているのは自明ですが、そのほかに GetEnumerator を 1 回呼び出したとき、
・enumerator が 1 つ
・mySlides が追加で 1 つ
増えることになります。
mySlides に関しては単純に ReleaseComObject の呼び出す数を 1 回増やすだけでも
なんとかなりますが、
enumerator の方については、For Each 構文を使った場合
For Each に完全に隠蔽されているので、解放できないと言うことになってしまいます。
ですので、For Each は使用せず、GetEnumerator 及び While ループを使う必要があります。

また、この GetEnumerator で取得した IEnumerator は、
ラップされたマネージなクラスインスタンス(非 COM オブジェクト)だと思います。
// Marshal.IsComObject メソッドで確認してみてください。
IEnumerator 自体が COM オブジェクトでないのであれば、
ReleaseComObject に直接渡すことはできません。
その場合、代わりにこの IEnumerator は
ICustomAdapter インターフェイスも実装しているはずです。
enumerator を ICustomAdapter に TryCast してその GetUnderlyingObject メソッドを呼び出せば、
この列挙子の COM オブジェクト実体が取得でき、ReleaseComObject に渡すことができます。

あと気になるところとしては、
ローカル変数に Nothing を代入する意味は(ほぼ)無いってことでしょうかね。

[ツリー表示へ]
タイトルRe^2: PowerPointのオブジェクト開放ができません
記事No9293
投稿日: 2009/08/26(Wed) 23:05
投稿者camputer
 Hongliang様

 ご親切にどうもありがとうございます!
今.IsComObjectについて調べております.
>For Each は、コンパイル時に GetEnumerator の呼び出しに変換されます。
というのは全然知りませんでした.以前Excelで同様に
 For Each Sheet In Sheets
      For Each Shape In Shapes
            
      Next
  Next
としたことがあり,こちらは花ちゃんの「Excelが解放されない」の記事のおかげで
きれいに解放できるコードが完成していたので,For〜Next自体の問題性については
全然疑ってもおりませんでした.

ご提案いただいた
>While ループを使う

>enumerator を ICustomAdapter に TryCast して〜
のコーディングを検索・自作してみて,再度ご報告させていただきます!
(知らないことが多そうなので頑張ります!^^)

 オブジェクト解放についてはExcelで散々既出なので,
アドバイスいただけるかどうか心配でした^^;
どうもありがとうございました!!

[ツリー表示へ]
タイトルRe^3: PowerPointのオブジェクト開放ができません
記事No9301
投稿日: 2009/08/27(Thu) 14:28
投稿者camputer
 Hongliang様

 camputerです。アドバイスどうもありがとうございました!
(最初の投稿と今いる場所が違うのでアドレスが違うかもしれません。どうもすみません。)

 IEnumeratorが非COM オブジェクトかどうかということで
下のようにMsgBoxを入れてみましたところ
            Dim enumerator As IEnumerator = mySlides.GetEnumerator()
            MsgBox(Marshal.IsComObject(enumerator))
            While enumerator.MoveNext()
                MsgBox(Marshal.IsComObject(enumerator))
                Dim mySlide As Object = enumerator.Current
                MRComObject(mySlide)
            End While
すべてFalseが帰ってきたので、すべて「非COM オブジェクト」
ということだと思うのですが。。
そこでアドバイスいただいた通りに作ってみたいので、
            Dim enumerator As IEnumerator = mySlides.GetEnumerator()
            While enumerator.MoveNext()
                Dim mySlide As Object = enumerator.Current
                Dim obj As Object = TryCast(enumerator, ICustomAdapter)
                Dim obj2 As Object = obj.GetUnderlyingObject
                MRComObject(mySlide)
                MRComObject(obj)
                'MRComObject(obj2)
            End While

            MRComObject(enumerator) : enumerator = Nothing
            MRComObject(mySlides) : mySlides = Nothing
            MRComObject(mySlides) : mySlides = Nothing
としましたが、obj2のMRComObjectの仕方が分かりません。。
whileの外に出してしまうと、「宣言されていません」とエラーになり、
whileの中にあるとwhile2週目がループしなくなってしまいます。。
(Whileの行で「基になる RCW から分割された COM オブジェクトを使うことはできません。」となります。)

 お時間のあるときにアドバイスいただけないでしょうか。。。
お手数をおかけいたしますがどうぞよろしくお願いいたします。

[ツリー表示へ]
タイトルRe^4: PowerPointのオブジェクト開放ができません
記事No9302
投稿日: 2009/08/27(Thu) 16:07
投稿者Hongliang
> Dim enumerator As IEnumerator = mySlides.GetEnumerator()
> While enumerator.MoveNext()
>     Dim mySlide As Object = enumerator.Current
>     Dim obj As Object = TryCast(enumerator, ICustomAdapter)
>     Dim obj2 As Object = obj.GetUnderlyingObject
>     MRComObject(mySlide)
>     MRComObject(obj)
>     'MRComObject(obj2)
> End While

enumerator の実体が 1 つ、解放すべき「COM への参照」を持っています。
キャストしても別に enumerator が増えるわけではなくインターフェイスが変わるだけなので、別に COM への参照は増えません。
そもそも実際に COM への参照を持っているのは GetUnderlyingObject で取得したオブジェクトですし。
で、While 文によって何回か中の節が呼び出されるわけです。
このままだと呼び出されただけ enumerator が COM への参照を解放してしまいます。
でも、解放すべき COM への参照は最初に言ったとおり 1 つだけです。

要するに enumerator が持つ COM への参照の解放は
While が終わった後に 1 回行うだけでいいってことです。

[ツリー表示へ]
タイトルRe^5: PowerPointのオブジェクト開放ができません
記事No9303
投稿日: 2009/08/27(Thu) 19:56
投稿者camputer
 Hongliang 様

 どうもありがとうございます!下のコードでSlideもShapeも解放することができました!^^
            mySlides = myPre.Slides
            Dim mySlidesNum As IEnumerator = mySlides.GetEnumerator()
            While mySlidesNum.MoveNext()
                Dim mySlide As Object = mySlidesNum.Current

                myShapes = mySlide.Shapes
                Dim myShapesNum As IEnumerator = myShapes.GetEnumerator()
                While myShapesNum.MoveNext()
                    Dim myShape As Object = myShapesNum.Current

                    MRComObject(myShape)
                End While
                Dim obj3 As Object = TryCast(myShapesNum, ICustomAdapter)
                Dim obj4 As Object = obj3.GetUnderlyingObject
                MRComObject(obj3) : obj3 = Nothing
                MRComObject(obj4) : obj4 = Nothing
                MRComObject(myShapesNum) : myShapesNum = Nothing
                MRComObject(myShapes) : myShapes = Nothing

                MRComObject(mySlide)
            End While
            Dim obj1 As Object = TryCast(mySlidesNum, ICustomAdapter)
            Dim obj2 As Object = obj1.GetUnderlyingObject
            MRComObject(obj1) : obj1 = Nothing
            MRComObject(obj2) : obj2 = Nothing
            MRComObject(mySlidesNum) : mySlidesNum = Nothing
            MRComObject(mySlides) : mySlides = Nothing
            'MRComObject(mySlides) : mySlides = Nothing'←この行は必要でしょうか??

 勘違いしているかもしれないのですが,前のコメントで教えていただいた
>・mySlides が追加で 1 つ
>増えることになります。
については,MRComObject(mySlides) : mySlides = Nothingを2回やる必要があるのでしょうか?
上のコードでいろいろテストしておりますと,今回は一回やるだけで何故か解放できたのですが,
そもそもReleaseComObject で解放する必要がある参照が増えているとわかっているのであれば,
今回の状況で解放できたからといって,むやみにコメントアウトしない方が
いいのかな?などと考えております.
 また,以前Excelで同様に↓のようなコードを書いたときはExcel.EXEが残らなかったのですが,
For Each mySheet In mySheets
Next
こちらについてもきちんと今回教えていただいたような書き直しておく方が無難でしょうか?
 現在,チーム内の事務仕事を効率化するためのアプリを作成中でございますが,
他の人のPCで起動したときなどにエラーが出る可能性を懸念しております.
徹底的に解放されやすいコードにしておくものなのでしょうか???
(今回教えていただいたような事もそうでしたが,VBなどのオブジェクト思考Prog.は,
他の計算Prog.などと違い,裏でどのような動きをしているのかまったく見当がつきません...)


 度々の質問で申しわけございませんが,今回Hongliang様に聞いておかないと,
今後ずっと間違った認識のままコーディングしそうです...
 お時間のある時に是非ともアドバイスいただきたくお願い申しあげます.
 どうぞよろしくお願いいたします.

[ツリー表示へ]
タイトルRe^6: PowerPointのオブジェクト開放ができません
記事No9304
投稿日: 2009/08/27(Thu) 22:04
投稿者Hongliang
解決おめでとうございます。
正直なところ PowerPoint どころか Excel も手元にないので、プロセスが終了するかどうかの確認はしてませんでした。
// 参照カウンタの確認は IE のオートメーションで代用してました。

> Dim myShapesNum As IEnumerator = myShapes.GetEnumerator()
> Dim obj3 As Object = TryCast(myShapesNum, ICustomAdapter)
> Dim obj4 As Object = obj3.GetUnderlyingObject
> MRComObject(obj3) : obj3 = Nothing
> MRComObject(obj4) : obj4 = Nothing
書くの 3 回目のような気もしますが。
myShapesNum、obj3、obj4 とありますが、
これらは全て同一にして単一の COM オブジェクトを指しています。
違いは、
obj4 : 列挙処理を実装する生の COM オブジェクト
myShapesNum : .NET で列挙処理を行うためのインターフェイス
obj3 : 生の COM オブジェクトを取得するためのインターフェイス
であり、myShapesNum 及び obj3 は生の COM オブジェクトではなく、ラップされたクラスです。
適当なクラス実装のイメージはこんな感じ。

Class ComObjectEnumerator
    Implements IEnumerator, ICustomAdapter
    Private _comObject As Object
    Public Function GetUnderlyingObject() As Object Implements ICustomAdapter
        Return _comObject
    End Function
    Public Function MoveNext() As Boolean Implements IEnumerator
        ' _comObject を使って実装する
    End Function
    ... ' 他のインターフェイスの実装は省略
End Class

myShapesNum 及び obj3 は ComObjectEnumerator のインスタンスそのもので、
obj4 が _comObject です。obj4 だけ違うインスタンスですね。
ReleaseComObject が必要なのは生の COM オブジェクトである obj4 のみであり、
純粋なマネージオブジェクトである obj3 は ReleaseComObject に渡せません。
(MRComObject メソッドでは内部に IsComObject 判定があるので問題ありませんが、
不要であることは明らかなので呼び出すべきではないと思います)

> >・mySlides が追加で 1 つ
> >増えることになります。
> については,MRComObject(mySlides) : mySlides = Nothingを2回やる必要があるのでしょうか?

ReleaseComObject メソッドは参照カウントを 1 減らしますが、
そのときまだ残っている参照カウントを返します。
MRComObject メソッドでは、Debug.WriteLine を使ってこの残りの参照カウントを出力しているので、
デバッグ時にこれで ReleaseComObject が余計に必要かどうか確認できます。
// そういう意味で、MRComObject は Function As Integer にして count を返すようにした方が良いかも。
また、MRComObject は第二引数に強制的に全ての参照を解放する引数を指定できるようなので、
問答無用でこれを呼ぶことも考えられます。
ローカル変数でメソッド外部に出すこともない変数ならそれでも特に問題ないでしょう。
あと、
MRComObject(mySlides) : mySlides = Nothing
を 2 回やったら 2 回目は必ず失敗(MRComObject 先頭で Return)しますよ。Nothing 代入済みですから。
前にも言いましたが Nothing の代入は特にローカル変数の場合意味がほぼ無いので、あまりお勧めしません。

>  また,以前Excelで同様に↓のようなコードを書いたときはExcel.EXEが残らなかったのですが,
> For Each mySheet In mySheets
> Next
> こちらについてもきちんと今回教えていただいたような書き直しておく方が無難でしょうか?

プロセスが残りそうなアウトプロセスサーバが手元にないっぽいので、私からは何とも言えません。
理屈はさんざん述べてきましたが、個々の案件によって違いがあることは考えられます。
しかし web をあさってみる限り、Excel の各コレクションオブジェクトについては
みんな For Each で処理して特に問題も起こってないみたいですね。

[ツリー表示へ]
タイトルRe^7: PowerPointのオブジェクト開放ができません
記事No9305
投稿日: 2009/08/27(Thu) 23:24
投稿者camputer
 Hongliang 様

 どうもありがとうございす!!
 勉強できることが増えて非常にありがたいコメントでございます.
obj1〜4のご親切な解説が私にとってはすごく助かります.^^

>Excel の各コレクションオブジェクトについては
>みんな For Each で処理して特に問題も起こってないみたいですね
 教えていただきどうもありがとうございます.同じようなコーディングでも
PPTとEXCELで全然違う挙動をするんですねー.VBは,コーディング途中では
それの挙動を予測しきれない言語のようなイメージを受けます.どこまで動作確認しても
他人に使ってもらう立場としては不安です..機能の多い言語故起こる現象でしょうけど.

 今までネットの検索だけで勉強・コーディングし続けてきましたが
数か月前から,検索だけでは理解できない問題が増えてきて,特に本案件には
ずっと悩み続けておりました.(今日の解決までに4か月かかりました...orz)
 私のようなレベルの人間にとって,花ちゃんの掲示板のような上級者のたまり場は
非常に心強く助かります.
Hongliang様のような,お詳しい方に閲覧していただけてよかったです.
どうもありがとうございました!!

[ツリー表示へ]
タイトルRe^7: PowerPointのオブジェクト開放ができません
記事No9307
投稿日: 2009/08/28(Fri) 10:50
投稿者魔界の仮面弁士
> >  また,以前Excelで同様に↓のようなコードを書いたときはExcel.EXEが残らなかったのですが,
> > For Each mySheet In mySheets
> > Next
> > こちらについてもきちんと今回教えていただいたような書き直しておく方が無難でしょうか?

AppDomain がアンロードされれば、ReleaseComObject されなかったオブジェクトも
解放される仕様になっています。

> しかし web をあさってみる限り、Excel の各コレクションオブジェクトについては
> みんな For Each で処理して特に問題も起こってないみたいですね。
や。Excel でも駄目ですよ。
(ゆえに私は、For Each ではなく For で呼び出しています)

多少無理やりな方法としては、

Dim o As Object = objWorksheets
For Each x In o
  MRComObject(x)
Next
MRComObject(o)

のように、一度 Object 型に受けてから解放という手もありますが、
あまり良い方法でもないですね。


なお、WithEvents 変数は、ここのサイトで紹介されている MRComObject の実装だと
正しく処理できない可能性があるので、イベントを使う場合には注意が必要かも。

[ツリー表示へ]
タイトルRe^8: PowerPointのオブジェクト開放ができません
記事No9315
投稿日: 2009/08/28(Fri) 17:43
投稿者camputer

 魔界の仮面弁士 様

 どうもありがとうございます!!
 私にとってVB博士のような存在でございます。>魔界の仮面弁士様
たまにマイナーな深いプロパティなどを検索にかけてみても
ほとんど例文が出てこないような際、ずっとねばって探し続けていると、
いつも魔界の仮面弁士様のコードに辿り着きます。
 この度はアドバイスいただきどうもありがとうございます。

>一度 Object 型に受けてから解放という手もありますが、
>あまり良い方法でもないですね。
 教えていただいてどうもありがとうございます。
私も自分なりに手探りでやってみまして、(↓私の最初のコメントより)
>For文の直前でDim mySlide As Object
>としてみると、たまにですが開放できるようになったのですが、
といったような現象になりました。おっしゃられる通りちょっと無理やりな方法だったのですね。。。

>や。Excel でも駄目ですよ。
>(ゆえに私は、For Each ではなく For で呼び出しています)
 どうもありがとうございます。過去作ったコードもすべて丁寧に直していきます。
「Forで呼び出し」とおっしゃいますと
For i = 1 To Slides.Shapes.Countのような感じでございましょうか。
今から工夫して作ってみます。

 勉強になるアドバイスをどうもありがとうございます。
数値解析コードばかり組んでいた自分にとっては、VBはそのカラクリガ非常に新鮮で
検索してみても例文どころか解決手段すら見つけられない状態の時がありますが、
アドバイスをいただけると道が開けるので非常にうれしいです。
 今後もどうぞよろしくお願いいたします。

[ツリー表示へ]
タイトルRe^9: PowerPointのオブジェクト開放ができません
記事No9358
投稿日: 2009/09/07(Mon) 15:39
投稿者魔界の仮面弁士
> For i = 1 To Slides.Shapes.Countのような感じでございましょうか。

違います。
それだと、「Slides.Shapes」から返されるオブジェクトを解放できません。

基本的に、「.」が 2 回以上連続して使われていたら、解放漏れの危険性があります。
(名前空間を指定するための「.」であれば、幾ら続いても構いませんが)

[ツリー表示へ]
タイトルRe^10: PowerPointのオブジェクト開放ができません
記事No9355
投稿日: 2009/09/07(Mon) 14:29
投稿者camputer

 魔界の仮面弁士 様

 どうもありがとうございます!
どうもすみませんでした。はしょっていました。
    myShapes = mySlide.Shapes            
    Dim iShapeMax As Integer = myShapes.Count
        For iShape = 1 To iShapeMax
          myShape = myShapes(iShape)


                    MRComObject(myShape)
    Next
        MRComObject(myShapes) : myShapes = Nothing

ですね。「.」を2つ続けない事については、花ちゃんでしっかり
勉強させていただきました。どうもありがとうございます。

 こちらの質問をしっかりクローズしてから別の質問をさせていただいたつもりでしたが、
スレッドを2つも立てているような感じになってしまいまして、どうもすみません。
>花ちゃん様、魔界の仮面弁士様

 変なコードを過去ログに残してしまうところでした。
ご指摘いただきどうもありがとうございました!!

[ツリー表示へ]