tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトルExeファイルの挙動が途中で止まる
記事No12024
投稿日: 2020/03/12(Thu) 21:20
投稿者mogi
いつもお世話になっております。

使用言語 :vb.net
開発環境 :Visual Studio 2017
実行環境 :Microsoft Windows 10 
使用ソフト:Excel 2019

ビルドが通ったExeファイルを起動すると、最初の2回までは適切に処理され、
正常に終了するのですが、3回目以降の起動で途中で停止状態になります。

本プログラムはテンプレートのExcelファイルを読み込み、それに対し、
書き込みを行った後、PDF出力を行うプログラムとなっております。
(PDFの出力にはExportAsFixedFormat メソッドを使用しています。)

停止状態になった際に、タスクマネージャーでExcelのCPU使用率が0%になっていることが
確認出来たため、Excelが原因のバグであると判断しました。

当初はComObjectの解放漏れが原因であると考えていたのですが、プログラムを確認すると
解放漏れはなく、Excelは正常に終了しておりました。
(プログラムをデバッグモードで起動し、プログラム内でExcelを終了させた後、タスクマネージャー上
 のExcelが終了したことを「解放漏れは無い」という根拠としております。)

また、デバッグモードで実行するとPDF出力時に停止していることがわかりました。
しかしながら、該当箇所を確認すると、
「ネイティブフレームが呼び出し履歴の最初にあるため式を評価できません。」
となっており原因の特定には至りませんでした。

そういうわけで停止する原因がわからず、途方に暮れております。

以下2点質問させてください。
@1回目は正常に終了し、2回目以降にExcelが原因でプログラムが停止する理由には何が考えられるか。
A「ネイティブフレームが呼び出し履歴の最初にあるため式を評価できません。」というのは
  デバッグモードで式が最適化されているため適切に評価出来ないという理解であっているか。

その他必要な情報等ありましたらお申しつけ下さい。
拙文で申し訳ありませんが、よろしくお願いします。

[ツリー表示へ]
タイトルRe: Exeファイルの挙動が途中で止まる
記事No12025
投稿日: 2020/03/13(Fri) 15:17
投稿者魔界の仮面弁士
> 本プログラムはテンプレートのExcelファイルを読み込み、それに対し、
> 書き込みを行った後、PDF出力を行うプログラムとなっております。
> (PDFの出力にはExportAsFixedFormat メソッドを使用しています。)

Excel を参照設定して呼び出しているのですね。
現象を再現可能な短い実験用プログラムを提示することはできますか?
また、Excel は Visible = True にて動作させていているでしょうか。


> 当初はComObjectの解放漏れが原因であると考えていたのですが、プログラムを確認すると
> 解放漏れはなく、Excelは正常に終了しておりました。

コードを見ていないので何とも言えませんが、すべての COM オブジェクトに対して
処置を行っていて、それでも動作不良を引き起こしているのであれば、
RelaseComObject メソッドの戻り値が「0」になっていることを確認してみてください。

すべての RelaseComObject 呼び出しにおいて、
0 が返される実装になっている必要があります。

0 未満の値になった時は、どこかで過解放を引き起こしています。
1 以上の場合は、型指定のミスなどにより参照カウントが予期せず増加することで、
解放不良を引き起こしている可能性があります。


> 1回目は正常に終了し、2回目以降にExcelが原因でプログラムが停止する理由には何が考えられるか。
Selection や Active 何某に頼ったコードを書いていると、Delete 処理などのタイミングで、
何も選択されていない状態が一時的に引き起こされることで Exception に繋がることが
ありますが…今回の事象はそれとは違っている印象。


良くあるのは、コーディングミスによって暗黙の Global オブジェクトへの参照が発生し、
それが残ってしまうケースです。この事故は .NET 空の呼び出しだけではなく、
VB6 からの呼び出しであっても生じます。下記は VB6 向けの情報ですが参考までに。
hhttps://support.microsoft.com/ja-jp/help/178510/excel-automation-fails-second-time-code-runs

なお、参照設定せずにレイトバインドで呼び出している場合は、
Global オブジェクトへの暗黙的な参照が発生することが無いため、
この問題には行き当たらないはずです。(レイトバインドだと参照カウントが
予期せず増加しやすいので、通常は型を明示したアーリーバインドにすることを推奨)


> A「ネイティブフレームが呼び出し履歴の最初にあるため式を評価できません。」というのは
スレッドの Abort 等によってその表示になるケースだとか、あるいは
一時停止していた時間が長すぎて COM 通信がタイムアウトで続行できなくなったりなど、
幾つかのパターンで発生するようですが、具体的な理由までは分からないですね。

>   デバッグモードで式が最適化されているため適切に評価出来ないという理解であっているか。
最適化されるのはどちらかというと Debug ビルドよりも Release ビルドな気がします。

[ツリー表示へ]
タイトルRe^2: Exeファイルの挙動が途中で止まる
記事No12026
投稿日: 2020/03/15(Sun) 13:52
投稿者mogi
魔界の仮面弁士さん、前回に引き続きありがとうございます
返信が遅くなり申し訳ありません。
魔界の仮面弁士さんの助言をもとに現在、ソースコードをなぞっています。

> Excel を参照設定して呼び出しているのですね。
> 現象を再現可能な短い実験用プログラムを提示することはできますか?

申し訳ありません。
どういうわけか再現プログラムが上手くいかず(再現できず)。
もう少しお時間を頂ければ再現できるかもしれません…。

> また、Excel は Visible = True にて動作させていているでしょうか。

xlapp.Visible = trueのことでしょうか。
以前も進言頂いたのにも関わらず、Visible = Trueで実行しておりませんでした。
早速Visible = Trueとさせて頂きましたが、
表にExcelが立ち上がってきてしまいます。
表示状態にすると必ずこうなってしまうのでしょうか。

> コードを見ていないので何とも言えませんが、すべての COM オブジェクトに対して
> 処置を行っていて、それでも動作不良を引き起こしているのであれば、
> RelaseComObject メソッドの戻り値が「0」になっていることを確認してみてください。
>
> すべての RelaseComObject 呼び出しにおいて、
> 0 が返される実装になっている必要があります。
>
> 0 未満の値になった時は、どこかで過解放を引き起こしています。
> 1 以上の場合は、型指定のミスなどにより参照カウントが予期せず増加することで、
> 解放不良を引き起こしている可能性があります。

ありがとうございます。
現在調査しております。過解放に関しては意識しておりませんでした。
解放漏れだけでは無く、過解放が原因で止まってしまう可能性もあるのですね。

> Selection や Active 何某に頼ったコードを書いていると、Delete 処理などのタイミングで、
> 何も選択されていない状態が一時的に引き起こされることで Exception に繋がることが
> ありますが…今回の事象はそれとは違っている印象。

不勉強で申し訳ありませんが、
『Selection や Active 何某』とはなんのことなのでしょうか。
今回の事象とは関係なさそうとのことですが少し気になり…。

> 良くあるのは、コーディングミスによって暗黙の Global オブジェクトへの参照が発生し、
> それが残ってしまうケースです。この事故は .NET 空の呼び出しだけではなく、
> VB6 からの呼び出しであっても生じます。下記は VB6 向けの情報ですが参考までに。
> hhttps://support.microsoft.com/ja-jp/help/178510/excel-automation-fails-second-time-code-runs

ありがとうございます。
提示して頂いた例を参考に現在調査しております。
ちなみに例ではCellsが何を参照にしているかを明示していないため、
解放漏れが起きているという理解であっていますでしょうか。

> > A「ネイティブフレームが呼び出し履歴の最初にあるため式を評価できません。」というのは
> スレッドの Abort 等によってその表示になるケースだとか、あるいは
> 一時停止していた時間が長すぎて COM 通信がタイムアウトで続行できなくなったりなど、
> 幾つかのパターンで発生するようですが、具体的な理由までは分からないですね。

なるほど、ありがとうございます。
ここから原因を探るのはやはり難しそうです…。

> >   デバッグモードで式が最適化されているため適切に評価出来ないという理解であっているか。
> 最適化されるのはどちらかというと Debug ビルドよりも Release ビルドな気がします。

お恥ずかしい。
確かにそうでした。

[ツリー表示へ]
タイトルRe^3: Exeファイルの挙動が途中で止まる
記事No12027
投稿日: 2020/03/15(Sun) 16:54
投稿者魔界の仮面弁士
現状は原因が分からないので、虱潰しに探すしかないのですよね。
元ソースの一部をコメントアウトするなどして、挙動を止める原因となる行が特定できれば
話が早いのですけれども。

-------

> 早速Visible = Trueとさせて頂きましたが、
> 表にExcelが立ち上がってきてしまいます。
> 表示状態にすると必ずこうなってしまうのでしょうか。

邪魔に思えるかもしれませんが、仮に、ダイアログの表示によって停止していた場合には、
表示状態にしておいた方が問題を発見しやすいため、可能であれば
デバッグ終了までは True 設定にすることをお奨めしています。

-------

> 『Selection や Active 何某』とはなんのことなのでしょうか。
> 今回の事象とは関係なさそうとのことですが少し気になり…。

Excel VBA でマクロの記録を行うと、Activate メソッドや Selection プロパティを
多用したコードが散見されます。同様の呼び出しを VB から行っていないか…という事です。

VB において、
 Me.TextBox1.Text = "NewText"
と記述するところで、
 Me.TextBox1.Select()
 Me.ActiveControl.Text = "NewText"
と記述したりはしないですよね。それと同じことです。

1 回目に呼び出した Selection と 2 回目に呼び出した Selection では、
処理タイミングによっては別のオブジェクトを指し示す可能性がありますので、
「現在アクティブなオブジェクト」に対する操作は不安定になりがちです。
.NET からの呼び出しであれば猶のこと。

-------


> ちなみに例ではCellsが何を参照にしているかを明示していないため、
> 解放漏れが起きているという理解であっていますでしょうか。

その通りです。先の例では、『 Excel.[Global].Cells 』へのアクセスと見なされるでしょう。

これが「VBA」であれば、「自分自身」に対するアクセスなのでさほど問題になりませんが、
VB からの呼び出しの場合、どのオブジェクトの Cells なのか曖昧になるため、障害となります。

下記のように、VB6 においても障害となりえます。
http://hanatyan.sakura.ne.jp/vb6/excel01.htm#no3


-------

「COM オブジェクトを引数に渡すタイプのメンバー」(メソッドやプロパティ)の
呼び出しは極力控えてください。

特に、引数を Object 型で受け渡した場合、COM の参照カウントが予期せず増加してしまい、
正しく解放されなかったり、以前に破棄したオブジェクト情報を内部的に保持し続けてしまうことで
2 回目以降の呼び出しが誤動作するなどの障害を引き起こす可能性があります。


-------

Excel のオブジェクト階層としては、たとえばセルへのアクセスなら

Application オブジェクト → Workbooks プロパティ
→ Workbooks コレクション → Open メソッド/Add メソッド
 → Workbook オブジェクト → Sheets プロパティ/Worksheets プロパティ
  → Sheets コレクション → インデクサ、あるいは For/For Each での列挙
   → Worksheet オブジェクト(グラフシートの場合は Chart)
    → Range オブジェクト → Cells プロパティ/Range プロパティなど

という階層構造を取るはずです。

Application.ActiveCell などのように、途中の階層を飛ばしてアクセスするメンバーなども
ありますが、可能な限り段階を踏み、それぞれの COM オブジェクトを確実に変数に受け取って
Marshal.ReleaseComObject に引き渡してください。
(もちろん、使用中のオブジェクトを破棄するのは NG ですが)

-------

コレクションの列挙処理も要注意です。特に For Each の場合、IEnumVARIANT という
COM インターフェイスを中継することになるので、使用する COM 相互運用機能アセンブリによっては、
解放しにくくなることがあるようです。(しかも列挙子が .NET 側でラッピングされているがゆえに、
IsComObject が False となってしまい、内部の COM オブジェクトにアクセスしにくい)

なので For Each を使っている箇所がある場合、可能であれば
For ループや Do ループへの置き換えを検討してみてください。
https://divakk.co.jp/aoyagi/csharp_tips_vssenum.html
http://hanatyan.sakura.ne.jp/vbnetbbs/wforum.cgi?mode=allread&no=9291

[ツリー表示へ]