DOBON.NET DOBON.NETプログラミング掲示板過去ログ

VS2008開発ソフトがグラフィック表示中に不規則にこける

環境/言語:[ Windows7 Professional 64bit SP1、VS2008 Basic、.NET Framework2.0]
分類:[.NET]

----------------------------------------------------------------
現症:グラフィック表示の途中でソフト(自社内作成)が不規則にこける
----------------------------------------------------------------
詳細:
表示用データを編集後、表示用フォーム(FormAとします)を開き
FormAのLoad→FormAのActivated→FormA.PictureBoxのPaint
という流れでグラフィック表示しています。
普段は正常に表示されますが、有る時いきなり
「'System.StackOverflowException' の
ハンドルされていない例外が
◇◇◇(商品名).dll で発生しました。」と
表示され、ソフトが中断されてしまいます。
このプログラムは、同じ社内で作成された
exe形式ファイルから呼び出されますが
一連の流れは同一DLL内で行われます。
「トラブルシューティングのヒント」では
「無限ループや無限再帰が無いことを確認します。」
と表示されますが、再帰呼び出しは行っていません。
怪しい部分は、ソースコードでの代入を行っていないのに
Paintイベントで参照するGraphicsオブジェクトが
いきなり初期状態に戻されてしまうことです。
このタイミングでこけるようです。
途中のソースコードをコメント化しても発生します。
同一データでも発生したりしなかったりで不規則です。
一旦発生すると発生しやすくなります。
タスクマネージャーには残っています。
PCを再起動すればとりあえず安定します。

この問題の解決方法をご存知の方はご教示ください。

---------------------------------------
□開発環境
Visual Studio 2008 Professional Edition
コンパイラの詳細設定
最適化を有効にする
ターゲットCPU AnyCPU
.NET FrameWork 2.0
OS Windows7 Professional 64bit SP1
DELL Optiplex980
RAM 8GB
---------------------------------------
□発生PC
Windows7,Windows8
---------------------------------------

よろしく御願いします。
■No32648に返信(h.hayashiさんの記事)

流石に、この情報で原因を特定するのは無理そうですが…思いつきで。

・Paint イベント内で e.Graphics を Dispose していないか。
・GDI+ オブジェクトを複数のスレッドで共有していないか。
・DoEvents を呼び出していないか(イベント再入の原因になりうる)


> 「'System.StackOverflowException' の
> ハンドルされていない例外が
その例外を捕えて、StackTrace を追跡することはできますか?
http://dobon.net/vb/dotnet/programing/unhandledexception.html


> .NET FrameWork 2.0
.NET FrameWork ではなく
.NET Framework ですね、公式表記では。
■No32649に返信(魔界の仮面弁士さんの記事)
> ■No32648に返信(h.hayashiさんの記事)

魔界の仮面弁士さん、早速のレスありがとうございます。

> ・Paint イベント内で e.Graphics を Dispose していないか。
Disposeしていません。

> ・GDI+ オブジェクトを複数のスレッドで共有していないか。
共有していません。

> ・DoEvents を呼び出していないか(イベント再入の原因になりうる)
呼び出していません。

>StackTrace を追跡することはできますか?
> http://dobon.net/vb/dotnet/programing/unhandledexception.html
StackTraceを入れてみましたが、追跡できませんでした。
ちなみに、ソースコードに Err.Erase を入れたら追跡できました。

> .NET Framework ですね、公式表記では。
タイプミスでした。

ソースコードの怪しい部分をブレークポイントに設定し
そこで一時停止した状態にて、F8キーで進めようとすると
「'System.StackOverflowException' の
ハンドルされていない例外が
◇◇◇(商品名).dll で発生しました。」と
表示され、コードにマウスポインタを合わせると
Graphicsオブジェクトのプロパティが全て、
「プロパティの評価に失敗しました。」と
表示されます。
該当のソースコードはこんな感じです。
関係なさそうな部分は省略します。
'----------------------------------------------------------------------
Private Sub FormA_Load(...) handles Me.Load
'PictureBox1は表示用
PictureBox1.Visible = False'デザイナーのデフォルトもFalseです
Me.Size = New Size(1024, Screen.PrimaryScreen.WorkingArea.Size.Height)
Me.Top = Screen.PrimaryScreen.WorkingArea.Top
'PictureBox1の縦横比を設定
PictureBox1.Height = PictureBox1.Width * (Math.Sqrt(2))
'gは表示用データの座標や文字列のポイント数を算出するためのもの
'グラフィック表示には使用しません
Dim g As Graphics = PictureBox1.CreateGraphics
'表示用データを編集する
'p_DateはDate型パブリック変数
'mDYは表示エリア上端からの相対座標
'pRCはByRef指定で編集結果をもらう
'lblPageはByrefのLabelオブジェクト
p_Date = modXXX.editData(p_ID, mDY, g, pRC, lblPage)
If xxx(判定条件) Then
...
g.Dispose
Else
...
g.Dispose
reInitControls(...,...) 'FormA上のボタンやスクロールバーを設定
End If
End Sub

Private Sub reInitControls(...,...)
PictureBox1.Hide()'念のため非表示に設定
'PictureBox1.Heightを調整
If xxx2(A4サイズで表示するか) then
'スクロールバーを表示
...
PictureBox1.Height = ...
...
Else'スクロールバーなしの表示
...
PictureBox1.Height = ...
...
End If
...
PictureBox1.Show()'これによりPictureBox1.Paintを発生させる
End Sub

Private Sub PictureBox1_Paint(..,e As ...PaintEventArgs) Handles PictureBox1.Paint
Me.paintX(e.Graphics)'表示する
End Sub

Private Sub paintX(ByVal g As Graphics)
PictureBox1.SuspendLayout()
Select Case (条件式)
Case 1
...
Case 2
modXXX.dispData(..., g, ...)
End Select
PictureBox1.ResumeLayout()
End Sub

Module modXXX
Private sub dispData(..., g, ...)
dispDataA(..., g, ...)
dispDataB(..., g, ...)
dispDataC(..., g, ...)
dispDataD(..., g, ...)
dispDataE(..., g, ...)
dispDataF(..., g, ...)
End Sub
End Class
'------------------------------------------------------------
※ もしかして
Dim g As Graphics = PictureBox1.CreateGraphics
が影響しているのでしょうか?
-------------------------------------------------------------
添付ファイル: captured.PNG (13 KB)
■No32649に返信(魔界の仮面弁士さんの記事)
> ■No32648に返信(h.hayashiさんの記事)

魔界の仮面弁士さん、早速のレスありがとうございます。

> ・Paint イベント内で e.Graphics を Dispose していないか。
Dispose していません。

> ・GDI+ オブジェクトを複数のスレッドで共有していないか。
共有していません。

> ・DoEvents を呼び出していないか(イベント再入の原因になりうる)
呼び出していません。

>StackTrace を追跡することはできますか?
> http://dobon.net/vb/dotnet/programing/unhandledexception.html
StackTrace を入れてみましたが、追跡できませんでした。
ちなみに、ソースコードに Err.Erase を入れたら追跡できました。

> .NET Framework ですね、公式表記では。
タイプミスでした。

ソースコードの怪しい部分をブレークポイントに設定し
そこで一時停止した状態にて、F8キーで進めようとすると
「'System.StackOverflowException' の
ハンドルされていない例外が
◇◇◇(商品名).dll で発生しました。」と
表示され、コードにマウスポインタを合わせると
Graphicsオブジェクトのプロパティが全て、
「プロパティの評価に失敗しました。」と
表示されます。
該当のソースコードを以下に示します。
関係なさそうな部分は省略してあります。
'----------------------------------------------------------------------
Private Sub FormA_Load(...) Handles Me.Load
'PictureBox1 は表示用
PictureBox1.Visible = False'デザイナーのデフォルトもFalseです
Me.Size = New Size(1024, Screen.PrimaryScreen.WorkingArea.Size.Height)
Me.Top = Screen.PrimaryScreen.WorkingArea.Top
'PictureBox1 の縦横比を設定
PictureBox1.Height = PictureBox1.Width * (Math.Sqrt(2))
'gは表示用データの座標や文字列のポイント数を
'MeasureStringで算出するためのものです
'グラフィック表示には使用しません
Dim g As Graphics = PictureBox1.CreateGraphics
'表示用データを編集する
'p_Date はDate型パブリック変数
'mDY は表示エリア上端からの相対座標
'pRC はByRef 指定で編集結果をもらう Public Structure 変数
'lblPage は ByRef の Label オブジェクト
p_Date = modXXX.editData(p_ID, mDY, g, pRC, lblPage)
If xxx(判定条件) Then
...
g.Dispose
Else
...
g.Dispose
reInitControls(...,...) 'FormA 上のボタンやスクロールバーを設定
End If
End Sub

Private Sub reInitControls(...,...)
PictureBox1.Hide()'念のため非表示に設定
'PictureBox1.Heightを調整
If xxx2(A4サイズで表示するか) then
'スクロールバーを表示
...
PictureBox1.Height = ...
...
Else'スクロールバーなしの表示
...
PictureBox1.Height = ...
...
End If
...
PictureBox1.Show()'これによりPictureBox1.Paintを発生させる
End Sub

Private Sub PictureBox1_Paint(..,e As ...PaintEventArgs) Handles PictureBox1.Paint
Me.paintX(e.Graphics)'表示する
End Sub

Private Sub paintX(ByVal g As Graphics)
PictureBox1.SuspendLayout()
'FormAが開かれる前に設定されるパブリック変数で下記条件式を判定するため、
'FormAを閉じない限りdispData1とdispData2のいずれか1つしか呼び出されません
Select Case (条件式)Case 1
...
modXXX.dispData1(..., g, ...)
Case 2
...
modXXX.dispData2(..., g, ...)
End Select
PictureBox1.ResumeLayout()
End Sub

Module modXXX
Private sub dispData2(..., g, ...)
dispDataA(..., g, ...)
dispDataB(..., g, ...)
dispDataC(..., g, ...)
dispDataD(..., g, ...)
dispDataE(..., g, ...)
dispDataF(..., g, ...)
End Sub
End Class
'--------------------------------------------------------------
※ dispDataDあたりで発生します。もしかして
Dim g As Graphics = PictureBox1.CreateGraphics
が影響しているのでしょうか?

dispData1内でもdispDat2と同様の処理を行うので発生する可能性はありますが、
不明です。
ロジック的にはdispData1とDispData2の違いは無視できると思います。
---------------------------------------------------------------
よろしく御願いします。
■No32651に返信(h.hayashiさんの記事)
> PictureBox1.Show()'これによりPictureBox1.Paintを発生させる

Paint イベントを発生させるのが目的ならば、
最後に Invalidate メソッドを呼ぶだけで良いかと。


> PictureBox1.SuspendLayout()
>  :
> PictureBox1.ResumeLayout()

これらを呼び出すということは、省略された部分に
「レイアウトの変更」を伴う処理を行っているということですよね。

ということは、Paint イベントの中で、レイアウト調整が必要なのですか?

コントロールのサイズなどが変われば、それによって
再度 Paint イベントが誘発されることになりかねません。
描画系とレイアウト調整は分離した方が良いと思いますよ。
2014/10/01(Wed) 17:01:33 編集(投稿者)

■No32652に返信(h.hayashiさんの記事)
> ロジック的にはdispData1とDispData2の違いは無視できると思います。

あるメソッドが、別のメソッドを呼び出す場合、そこに256バイト以上の
引数を渡す処理があると、デバッガの追跡が失敗して、

「ガベージ コレクションを実行できない場所で停止したため、式を評価できません。」
「プロパティの評価に失敗しました。」

などの状態に陥る事があるようです。

巨大な構造体を ByVal 渡ししているメソッドは無いでしょうか。
あるいは、非参照型なプリミティブ型を多数渡しているとか。


# 型付DataSetを使っていると、列数の多いテーブル等に対して、
# 大量の引数を持ったメソッドができたりするので要注意。


…この事象が、StackOverflow 例外の件と関係あるかどうかは分かりませんが。
魔界の仮面弁士さん、返信ありがとうございます。

■No32653に返信(魔界の仮面弁士さんの記事)
ご指摘とおり、描画系とレイアウト調整を分離しました。

■No32654に返信(魔界の仮面弁士さんの記事)
>巨大な構造体を ByVal 渡ししているメソッドは無いでしょうか。
>あるいは、非参照型なプリミティブ型を多数渡しているとか。
Marshal.SizeOf で確認したら、構造体で
一番大きいサイズが 23248
2番目が 6200 でした。

※不具合は直っていません。
他にも Integer型 の2次元配列(1000,2) があります。
ソースコードでの代入・参照時の簡素化のために(1000,2)を指定しています。
約3000個のうち、使用されるのは最大100箇所程度です。
この2次元配列を変えて、別のやり方でやってみるつもりです。
2014/10/03(Fri) 09:54:38 編集(投稿者)

■No32655に返信(h.hayashiさんの記事)
> Marshal.SizeOf で確認したら、構造体で
> 一番大きいサイズが 23248
> 2番目が 6200 でした。
そのサイズの構造体だと、コピーコストが高すぎます。
Structure ではなく Class に修正することを検討してみて下さい。
http://msdn.microsoft.com/ja-jp/library/ms229017.aspx

構造体で管理しなければならない理由は何かありますか?


> 約3000個のうち、使用されるのは最大100箇所程度です。
> この2次元配列を変えて、別のやり方でやってみるつもりです。
System.Collection.Generics 名前空間のクラスで管理できるかも知れませんね。
(ただしこの点を修正しても、StackOverflowException の問題には直接影響しなさそう)


> ※不具合は直っていません。
使用しているミドルウェアやライブラリによって、
特定の条件を満たしたときにスタックが不足する物があります。
自分の場合は、生体認証系の 某SDK にて発生する事象を経験しています。

本来は、既定のスタックサイズで実行されるよう開発するのが望ましいのですが、
外部要因などでそれが困難な場合は、スタックのサイズを増やして回避してみて下さい。

(1) スタートメニューから、Visual Studio コマンドプロンプトを起動し
  『DUMPBIN /headers C:\folderPath\yourAppName.exe』
  のコマンドを実行。(パスは自身の環境に合わせてください)

(2) "OPTIONAL HEADER VALUES" の下に、予約スタックサイズが表示されます。
  たとえば、「80000 size of stack reserve」と書かれていれば、
  &H80000 = 524,288 Byte = 512KB = 0.5MB というサイズですし、
  「100000 size of stack reserve」と書かれていたとしたら、
  &H100000 = 1,048,576 Byte = 1.0 MB というサイズです。

(3) 『EDITBIN /STACK:予約したいサイズ C:\folderPath\yourAppName.exe』
  のコマンドで、新たなスタックサイズを Byte 指定します。
  たとえば、スタックサイズとして 5MB を予約したい場合には、
  「/STACK:5242880」あるいは「/STACK:0x500000」と書きます。
  実験を繰り返して、丁度良いサイズを探してみてください。


スタックサイズを変更した後は、(2) のコマンドで確認しておいてください。
正しく変更されている事が分かれば、以降は (3) の処理のみを
Visual Studio の「ビルド後イベント」に仕込んでおくと良いでしょう。


なお、スタック値を増やしても問題が回避できないのであれば、
そもそものロジックに問題がある可能性が高いと予想できますが、
その要因がどこにあるのかは、現時点では情報不足で判断できません。

StackTrace を得られれば、原因の特定も早いのですけれどね…。
魔界の仮面弁士さん
度重なる返信ありがとうございます。

>そのサイズの構造体だと、コピーコストが高すぎます。
>Structure ではなく Class に修正することを検討してみて下さい。
>http://msdn.microsoft.com/ja-jp/library/ms229017.aspx
>構造体で管理しなければならない理由は何かありますか?

特にありません。
Structure ではなく
Class を使用するべきだった事を
初めて知りました。Structureは欲しい機能をネットで探して Class
で宣言されていたものはそのまま使いましたが、それ以外はほとんどStructureで
作っていました。とりあえず、FormA表示に関連する部分の Structure を
Class に修正中です。修正量が多いのでまだ日にちを要します。他のご指摘は
その後の懸案とさせていただきます。
前回の投稿から何日か経過しました。途中経過ですが、投稿します。
------------------------------------------------------------------------------
FormA表示に関連する Structure をできるだけ Class に修正し下記の動作チェックをしました。
------------------------------------------------------------------------------
FormAを開き、他の画面(FormSとします)へ戻り、再度FormAを表示する。
これを5000回くらい繰り返しても再発しませんでした。
他のPC2台での1000回でもOKでした。これを何日間か続けます。
これが正常ならばその後、修正前後の表示内容が同じであることの確認も行う予定です。
------------------------------------------------------------------------------
ちなみに Class に修正しなかった Structure は下記のようなかんじです。
-------------
Public Structure udfRc枠Andデータ
Public x_1_rect As RectangleF
Public x_2_strデータ As String
Public x_3_enum配置 As enumRc配置 'Enum ステートメント
Public x_4_enum枠情報 As enumRc枠情報 'Enum ステートメント
Public x_6_foreColor As Color
Public x_7_DX As Single '印字位置補正用(x座標)
Public x_8_DY As Single '印字位置補正用(y座標)
Public x_Y_intSizeMode As Integer
Public x_Z_str個別名 As String
End Structure
-------------
Public Structure udfRc_項目と点数
Public x_1_項目 As udfRc枠Andデータ 'Structure
Public x_2_Data点 As udfRc枠Andデータ 'Structure
End Structure
-------------
Public Structure udfRc_項目と回と点数
Public x_1_項目 As udfRc枠Andデータ 'Structure
Public x_2_Data回 As udfRc枠Andデータ 'Structure
Public x_3_回 As udfRc枠Andデータ 'Structure
Public x_4_Data点 As udfRc枠Andデータ 'Structure
End Structure
-------------
Public Structure udfRc_項目X回
Public x_1_項目 As udfRc枠Andデータ 'Structure
Public x_2_Data点数 As udfRc枠Andデータ 'Structure
Public x_3_strX As udfRc枠Andデータ 'Structure
Public x_4_Data回 As udfRc枠Andデータ 'Structure
Public x_5_str回 As udfRc枠Andデータ 'Structure
Public x_6_Data点 As udfRc枠Andデータ 'Structure
End Structure
-------------
Public Structure udfRc_項目と日と点数
Public x_1_項目 As udfRc枠Andデータ 'Structure
Public x_2_Data日 As udfRc枠Andデータ 'Structure
Public x_3_str日 As udfRc枠Andデータ 'Structure
Public x_4_Data点数 As udfRc枠Andデータ 'Structure
End Structure
-------------
Public Structure udfRc90N_日計X日X小計
Public x_1_データ日計 As udfRc枠Andデータ 'Structure
Public x_2_strX As udfRc枠Andデータ 'Structure
Public x_3_データ日数 As udfRc枠Andデータ 'Structure
Public x_4_str日 As udfRc枠Andデータ 'Structure
Public x_5_データ小計 As udfRc枠Andデータ 'Structure
End Structure
-------------
Public Structure udfRc97_N円XN回
Public x_1_データ金額 As udfRc枠Andデータ 'Structure
Public x_2_str円X As udfRc枠Andデータ 'Structure
Public x_3_データ回 As udfRc枠Andデータ 'Structure
Public x_4_str回 As udfRc枠Andデータ 'Structure
End Structure
-------------
Public Structure udfRcLine
Public x_1_区分 As udfRc枠Andデータ 'Structure
Public x_2_アスタリスク As udfRc枠Andデータ 'Structure
Public x_3_名称_ As udfRc枠Andデータ 'Structure
Public x_4_点数X回 As udfRc枠Andデータ 'Structure
Public x_5_n5Code As Integer
Public x_6_bln下線あり As Boolean
End Structure
-------------
Public Structure udfRcT_G
Public x_udfRcSLines() As udfRcLine 'Structure
End Structure
-------------
Public Structure udfRcT_N
Public x_udfRcSLines() As udfRcLine 'Structure
End Structure
-------------
Public Structure udfYM
Public x_1_Year As Integer
Public x_2_Month As Integer
End Structure
------------------------------------------------------------------------------
後日、経過報告します。
■No32665に返信(h.hayashiさんの記事)
前回の投稿から1週間経過しました。再投稿します。

a.開発PCでの FormA ←→ FormS の画面切り替えを5000回続けました。
b.他PCでも FormA ←→ FormS の画面切り替えを1000回続けました。

aとbを1週間(実質5日)かけて実行しましたが、こけませんでした。
魔界の仮面弁士様から他の示唆を頂きましたし、
本来は Structure → Class と直すべき部分も残っていますが、
なにぶんにもソースコードが膨大で、修正後は画面切り替えが安定しているため
ソースコードの修正は一応ここまでとします。

この後、同じデータを使って FormAでの表示内容 が修正前と後で
同じであることを確認します。同一であることが確認された時点、
または今回の修正に関連する不具合が発生した時に再投稿します。
■No32666に返信(h.hayashiさんの記事)
> 同一であることが確認された時点、
> または今回の修正に関連する不具合が発生した時に再投稿します。

丁寧な結果報告、ありがとうございます。


Structure を Class へと置き換えることで、
動作結果に改善が見られた、ということでしょうか。
(構造体が直接の原因かどうかはさておき)


No32655 [2014/10/02 18:19:53]
> 描画系とレイアウト調整を分離しました。
> ※不具合は直っていません。

No32665 [2014/10/14 17:51:45]
> FormA表示に関連する Structure をできるだけ Class に修正

No32666 [2014/10/21 11:26:40]
> Structure → Class と直すべき部分も残っていますが、
> ソースコードの修正は一応ここまでとします。
> Structure を Class へと置き換えることで、
> 動作結果に改善が見られた、ということでしょうか。
> (構造体が直接の原因かどうかはさておき)

改善されました。
FormA を ShowDialg で呼び出してから表示が完了するまでの時間も
短くなりました。今はほとんど瞬間的に表示されるというかんじです。
■No32668に返信(h.hayashiさんの記事)
日にちが経ったので再投稿します。

>この後、同じデータを使って FormAでの表示内容 が修正前と後で
>同じであることを確認します。同一であることが確認された時点、
>または今回の修正に関連する不具合が発生した時に再投稿します。

社内期限が来年の1月または4月なので
この記事についてとりあえず解決とします。
この件について何か問題が出たら別記事で再投稿します。

魔界の仮面弁士さん、ありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板