System.AccessViolationException

タグの編集
投稿者 ロト君  (社会人) 投稿日時 2021/12/18 01:03:31
VS19でVB.NETを使ってダイアログを使ってファイルのやり取りをしようとしています。
然しながら、最初はうまく行きますが、違う画面に遷移した後に、再度ダイアログを開こうとすると、エラーになります。

エラー内容

System.AccessViolationException: '保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます。

上記の内容です。
記載してるコードは下記のモノです。

    Private Sub btn_img_Click(sender As Object, e As EventArgs) Handles btn_img.Click

        'OpenFileDialogクラスのインスタンスを作成
        Dim ofd As New OpenFileDialog()

        'はじめのファイル名を指定する
        'はじめに「ファイル名」で表示される文字列を指定する
        ofd.FileName = Nothing
        'はじめに表示されるフォルダを指定する
        '指定しない(空の文字列)の時は、現在のディレクトリが表示される
        ofd.InitialDirectory = Nothing
        '[ファイルの種類]に表示される選択肢を指定する
        '指定しないとすべてのファイルが表示される
        ofd.Filter = "画像ファイル(*.jpg)|*.jpg"
        '[ファイルの種類]ではじめに選択されるものを指定する
        '2番目の「すべてのファイル」が選択されているようにする
        ofd.FilterIndex = 1
        'タイトルを設定する
        ofd.Title = "開くファイルを選択してください"
        'ダイアログボックスを閉じる前に現在のディレクトリを復元するようにする
        ofd.RestoreDirectory = True
        '存在しないファイルの名前が指定されたとき警告を表示する
        'デフォルトでTrueなので指定する必要はない
        ofd.CheckFileExists = True
        '存在しないパスが指定されたとき警告を表示する
        'デフォルトでTrueなので指定する必要はない
        ofd.CheckPathExists = True

        'ダイアログを表示する
        If ofd.ShowDialog = DialogResult.OK Then
            'OKボタンがクリックされたとき、選択されたファイル名を表示する
            txt_img.Text = ofd.FileName
            pic_img.SizeMode = PictureBoxSizeMode.Zoom
            pic_img.ImageLocation = ofd.FileName
        End If

    End Sub

どうしても、自分では解決できません。どなたか、ご教授お願い致します。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/12/18 08:35:30
AccessViolationException の原因は多岐に渡るので、なかなか難しいところですね。

新規プロジェクトで、OpenFileDialog を開くだけのアプリを作った場合にも
同様に障害が発生しますか? 特定のプログラムだけで問題が起きるのなら、
そのプログラムの別の場所に何か問題があるのかもしれません。

たとえば、何か外部の API を呼び出している箇所があって、それが
カレントディレクトリからの相対パスで依存ファイルを参照していた場合、
OpenFileDialog を通じて現在のディレクトリが変化することで問題が起きるとか。


あるいは、新規プロジェクトでも問題が起きているような場合、
VB ではなく、実行環境に問題がある可能性もあります。
(エンドポイントセキュリティや DRM 、シェルエクステンションなど)
https://social.msdn.microsoft.com/Forums/ja-JP/ba2bb142-4b19-4e20-aee0-ed8879f3bc74/124671253112488125251254012523openfiledialog?forum=csharpgeneralja
https://social.msdn.microsoft.com/Forums/ja-JP/d52ef8d8-e4a1-40f8-b7dc-17c80fcc3934/visual?forum=vsgeneralja

同時に動作しているサービスや常駐ソフトウェアを停止した状態で実行してみたり、
作成したプログラムを他の PC に持ち出して、そこで問題が再現するかも確認してみましょう。
投稿者 ロト君  (社会人) 投稿日時 2021/12/18 10:48:20
>新規プロジェクトで、OpenFileDialog を開くだけのアプリを作った場合にも
>同様に障害が発生しますか? 特定のプログラムだけで問題が起きるのなら、
>そのプログラムの別の場所に何か問題があるのかもしれません。

新規で作った場合は起きないようですね。
そうなると、エラーが起きてるプロジェクトに問題があるかもですね・・・・。

少し、調べてみます!!
投稿者 ロト君  (社会人) 投稿日時 2021/12/18 10:52:27
>>新規プロジェクトで、OpenFileDialog を開くだけのアプリを作った場合にも
>>同様に障害が発生しますか? 特定のプログラムだけで問題が起きるのなら、
>>そのプログラムの別の場所に何か問題があるのかもしれません。

>新規で作った場合は起きないようですね。
>そうなると、エラーが起きてるプロジェクトに問題があるかもですね・・・・。
>
>少し、調べてみます!!

調べてみましたら、同じプロジェクトでも、異なるフォームで呼べば、問題なかったですね。

そうなると、エラーが起きてるフォーム中の内容がバグの原因の様に感じますね・・・。
投稿者 ロト君  (社会人) 投稿日時 2021/12/18 11:16:46
色々調べてみましたら、自作クラスを通った後にエラーになってるようです。

自作クラスで、
Public Property Form_Name As New Form
という物を使っていますが、New Formで設定した場合。
Form_Name をClose() しないといけませんか??

下記のようなものです。




Imports System.Data.OleDb

Public Class ComboBox_Set

    Public Property Select_Table_Name As String
    Public Property Select_Name As String
    Public Property ComboBox_Name As String
    Public Property resultDt As New DataTable
    Public Property Form_Name As New Form

    Sub New()

        Select_Table_Name = Nothing
        Select_Name = Nothing
        ComboBox_Name = Nothing
        resultDt.Clear()

    End Sub

    Public Sub Clear()

        Select_Table_Name = Nothing
        Select_Name = Nothing
        ComboBox_Name = Nothing
        resultDt.Clear()

    End Sub

    Public Sub Maker()

        Dim db As New Connection_DB

        db.Sql.AppendLine("SELECT")
        db.Sql.AppendLine("t_env.key_name")
        db.Sql.AppendLine(",t_env.ID")
        db.Sql.AppendLine(",t_env.name1")
        db.Sql.AppendLine(",t_env.value1")
        db.Sql.AppendLine(",t_env.sort")
        db.Sql.AppendLine(",t_env.delflg")
        db.Sql.AppendLine("FROM t_env")
        db.Sql.AppendLine("WHERE t_env.delflg = 0")
        db.Sql.AppendLine("AND t_env.key_name ='" & Select_Name & "'")
        db.Sql.AppendLine("ORDER BY")
        db.Sql.AppendLine("t_env.key_name ASC")
        db.Sql.AppendLine(",t_env.sort ASC")

        db.Selecter()
        resultDt = db.resultDt

        Dim ctrl As ComboBox = FindControl(Form_Name, ComboBox_Name)

        'DataTableオブジェクトを用意
        Dim data_table As New DataTable()

        'DataTableに列を追加
        data_table.Columns.Add("ID", GetType(Long))
        data_table.Columns.Add("String_name", GetType(String))
        For i As Integer = 0 To resultDt.Rows.Count Step 1

            '新しい行を作成
            Dim row As DataRow = data_table.NewRow()

            '各列に値をセット
            If i = 0 Then
                row("ID") = 0
                row("String_name") = "未設定"
            Else
                row("ID") = resultDt.Rows(i - 1).Item(3)
                row("String_name") = resultDt.Rows(i - 1).Item(2).ToString
            End If

            'DataTableに行を追加
            data_table.Rows.Add(row)

        Next

        data_table.AcceptChanges()

        ctrl.DataSource = data_table
        ctrl.DisplayMember = "String_name"
        ctrl.ValueMember = "ID"

        'ComboBox のスタイルを指定します
        ctrl.DropDownStyle = ComboBoxStyle.DropDownList
        '先頭のアイテムを表示させる
        ctrl.SelectedIndex = 0

        Clear()

    End Sub



    Public Function FindControl(ByVal hParent As Control, ByVal stName As String) As Control
        ' hParent 内のすべてのコントロールを列挙する
        For Each hControl As Control In hParent.Controls
            ' 列挙したコントロールにコントロールが含まれている場合は再帰呼び出しする
            If hControl.HasChildren = True Then
                Dim hFindControl As Control = FindControl(hControl, stName)

                ' 再帰呼び出し先でコントロールが見つかった場合はそのまま返す
                If Not hFindControl Is Nothing Then
                    Return hFindControl
                End If
            End If

            ' コントロール名が合致した場合はそのコントロールのインスタンスを返す
            If hControl.Name = stName Then
                Return hControl
            End If
        Next hControl

    End Function

End Class
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/12/18 15:45:53
> 自作クラスを通った後にエラーになってるようです。
プロジェクトのプロパティの [アプリケーション] タブから
右下の[アプリケーション イベントの表示]を使い、
MyApplication の UnhandledException イベントにて、
e.Exception.StackTrace を記録してみたら、何か分かりませんか?


> Public Function FindControl(ByVal hParent As Control, ByVal stName As String) As Control
上記の再帰探索は、
 Return hParent.Controls.Find(stName, searchAllChildren:=True).FirstOrDefault()
の一行だけで済むかも。

> Public Property Form_Name As New Form

クラス内で生成されるフォームなのであれば、それを保持するプロパティは
Public ReadOnly Property にするべきなのでは…?

Writable にしておくということは、外部から Nothing を代入されたりすることも
許容することになるわけですが、それを考慮したコードには見えませんでした。
(Protected ならまだ分かりますが)

> Form_Name をClose() しないといけませんか??
表示していないなら Close() する必要は無いでしょう。
表示している、あるいは Hide() 状態であるなら、どこから処分すべきですが。

そもそも、この Form の処分処理は、どこに記載されているのでしょうか?
Form や Control は IDisposable なので、使用後には Dispose が必要です。

※ちなみに DataSet や DataTable も IDisposable ですが、それらは Dispose 不要です。

・モードレス表示の Form の場合は、フォームが閉じられたときに自動的に処分されます。
・モーダル表示の Form の場合は、開発者が自分で Dispose せねばなりません。(Using 推奨)
・フォーム上に貼られたコントロールは、親フォームの破棄時に、一緒に処分されます。
・Controls コレクションに加えられていないコントロールは、明示的な Dispose 処理が必要です。(Controls.Remove した時などに忘れがち)
・Timer などのコンポーネントも、Me.components に参加させていないならば、使用後に Dispose が必要です。

もちろん Application.OpenForms には加わりますので、アプリ終了時には解放されますが、
シャットダウンモード:「最後のフォームが閉じるとき」の設定にしてある場合、
非表示状態のフォームが残っていると、アプリを閉じたつもりでも、タスクマネージャー上に
プロセスが残り続けてしまう結果となります。

クラスの外部から Form 参照を得るのであれば、ComboBox_Set 側で
Form の破棄責任をとる必要はありませんが、ComboBox_Set 内で New しておき、
それをフィールド変数として保持するのであれば、ComboBox_Set クラスを
Component 継承クラスにするなどして、「Disposeパターン」にて実装するべきかと思います。
投稿者 (削除されました)  () 投稿日時 2021/12/18 16:41:39
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/12/18 16:45:35
最初の回答で指摘し忘れていたので追記:

> VS19
「VS19」というバージョンはまだリリースされていません。ご注意ください。
Visual Studio 2019 のことだとしたら、「VS2019」と記述すべきです。

VS17  …  Visual Studio 2022
VS16  …  Visual Studio 2019
VS15  …  Visual Studio 2017
VS14  …  Visual Studio 2015
VS12  …  Visual Studio 2013
VS11  …  Visual Studio 2012
VS10  …  Visual Studio 2010
VS9   …  Visual Studio 2008
VS8   …  Visual Studio 2005
VS7.1 …  Visual Studio.NET 2003
VS7   …  Visual Studio.NET (2002)
VS6   …  Visual Studio 6.0
VS97  …  Visual Studio 97


VB16.9 (VB2019) … Visual Studio 2019 16.9 … init-only プロパティを持つクラスの呼び出し
VB16.0 (VB2019) … Visual Studio 2019 16.0 … 行継続文字の後に書かれたコメント
VB15.8 (VB2017) … Visual Studio 2017 15.8 … 浮動小数点数の整数変換のパフォーマンス
VB15.5 (VB2017) … Visual Studio 2017 15.5 … 末尾以外の名前付き引数
VB15.3 (VB2017) … Visual Studio 2017 15.3 … 名前付きタプル推論
VB15.0 (VB2017) … Visual Studio 2017 15.0 … 参照戻り値の利用、タプル型リテラル、バイナリリテラル
VB14.0 (VB2015) … Visual Studio 2015      … 文字列補間、複数行文字列、NameOf 演算子、null 条件演算子
VB12.0 (VB2013) … Visual Studio 2013      … Roslyn 対応の準備
VB11.0 (VB2012) … Visual Studio 2012      … Async、Await、Yeild
VB10.0 (VB2010) … Visual Studio 2010      … 自動実装プロパティ、コレクション初期化子、暗黙的行継続
VB9.0  (VB2008) … Visual Studio 2008      … LINQ、XML リテラル、ローカル型推論、If 演算子、null許容値型
VB8.0  (VB2005) … Visual Studio 2005      … My 名前空間、部分クラス、Using、ジェネリック
VB7.1  (VB2003) … Visual Studio .NET 2003 … ビットシフト演算子、ループ変数スコープ
VB7.0  (VB2002) … Visual Studio .NET      … 最初の VB.NET リリース
投稿者 (削除されました)  () 投稿日時 2021/12/18 17:25:28
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/12/18 17:41:29
> Public Property Form_Name As New Form

自クラス内で Form を New していることにあまり意味が無く、
外部からフォームを渡すことを想定した設計で、
かつ、外部から Form_Name を Nothing にされることを
許容したくないのなら、プロパティを引数に変更すると管理しやすいかと。


 '案1:コンストラクタでのみフォームを指定可能とする
 Public ReadOnly Property Form_Name As Form  'ReadOnly に変更(または Private にする)
 Public Sub New(targetForm As Form)
  If targetForm Is Nothing Then New ArgumentNullException()
  _Form_Name = targetForm
 End Sub

 '案2:Form をフィールド変数やプロパティとして持たず、メソッドローカル内に留める
  Public Sub Maker(Form_Name As Form)


> Dim ctrl As ComboBox = FindControl(Form_Name, ComboBox_Name)
右辺が As Control なのに左辺が As ComboBox になっているため、
これだと、Option Strict On の時にエラーになってしまいます。

代入式の右辺を
 = DirectCast(FindControl(Form_Name, ComboBox_Name), ComboBox)
もしくは
 = Form_Name.Controls.Find(ComboBox_Name, True).OfType(Of ComboBox).FirstOrDefault()
にすれば、Option Strict On にも対応できるかと思います。


> '各列に値をセット
> If i = 0 Then
ループ内で毎回 i の値をゼロチェックするよりも、
最初に追加してからループした方が手っ取り早そうです。

data_table.Rows.Add(0L, "未設定")
For Each dbRow In resultDt.Rows
   Dim newRow As DataRow = data_table.NewRow()
   newRow("ID") = dbRow.Item(3)  'value1 
   newRow("String_name") = dbRow.Item(2).ToString()  'name1 
   data_table.Rows.Add(newRow)
Next
data_table.AcceptChanges()



※本題と関係ない所にばかり反応…
投稿者 ロト君  (社会人) 投稿日時 2021/12/18 19:10:28
色々と、アドバイスありがとうございます!!

UnhandledExceptionの確認ですが、こんな感じで出力出来ますか??


Namespace My

    Partial Friend Class MyApplication

        Private Sub MyApplication_UnhandledException(
            ByVal sender As Object,
            ByVal e As Microsoft.VisualBasic.ApplicationServices.
                UnhandledExceptionEventArgs) _
            Handles Me.UnhandledException

            Dim trace As String = Environment.StackTrace
            Console.WriteLine(trace)
        End Sub

    End Class

End Namespace


アプリが落ちても、上手く取得できません><
投稿者 ロト君  (社会人) 投稿日時 2021/12/18 19:13:40
一応、最後の出力の部分には下記のモノが書いてあります。




シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
プログラム '[9800] VB_ASystem.exe' はコード -1 (0xffffffff) で終了しました。
投稿者 ロト君  (社会人) 投稿日時 2021/12/18 19:20:38
別件の内容ですが、一行で行けました。

>> Public Function FindControl(ByVal hParent As Control, ByVal stName As String) As Control
>上記の再帰探索は、
> Return hParent.Controls.Find(stName, searchAllChildren:=True).FirstOrDefault()
>の一行だけで済むかも。

    Public Function FindControl(ByVal hParent As Control, ByVal stName As String) As Control

        Return hParent.Controls.Find(stName, searchAllChildren:=True).FirstOrDefault()

    End Function

ですね!
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/12/18 19:56:35
> アプリが落ちても、上手く取得できません><

なるほど。AccessViolationException が、
破損状態例外(CSE) に当たるので捉えられないのかな…。

セキュリティ上の理由により、既定では破損状態例外(CSE)を
捕捉できないように制限が加えられています。
https://dobon.net/vb/dotnet/programing/unhandledexception.html#applicationunhandledexception

app.config に legacyCorruptedStateExceptionsPolicy を追加するか、
対象のメソッドに HandleProcessCorruptedStateExceptions 属性を付与してみてはどうでしょう。
https://tzeditor.blogspot.com/2019/11/blog-post_8.html
投稿者 ロト君  (社会人) 投稿日時 2021/12/19 08:41:24
>なるほど。AccessViolationException が、
>破損状態例外(CSE) に当たるので捉えられないのかな…。

>セキュリティ上の理由により、既定では破損状態例外(CSE)を
>捕捉できないように制限が加えられています。
>https://dobon.net/vb/dotnet/programing/unhandledexception.html#applicationunhandledexception

>app.config に legacyCorruptedStateExceptionsPolicy を追加するか、
>対象のメソッドに HandleProcessCorruptedStateExceptions 属性を付与してみてはどうでしょう。
>https://tzeditor.blogspot.com/2019/11/blog-post_8.html


legacyCorruptedStateExceptionsPolicy などを試しましたが、ダメでした・・・。
う~~~ん。難しいですね・・・。
投稿者 ロト君  (社会人) 投稿日時 2021/12/19 09:13:54
どうしても、ダイアログでは2回目の更新が出来ないようなので、
ドロップ形式に変更しました。

コードは下記の様にしました。



    Private Sub txt_img_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles txt_img.DragEnter

        'ファイル形式の場合のみ、ドラッグを受け付けます。
        If e.Data.GetDataPresent(DataFormats.FileDrop) = True Then
            e.Effect = DragDropEffects.Copy
        Else
            e.Effect = DragDropEffects.None
        End If

    End Sub

    Private Sub txt_img_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles txt_img.DragDrop

        'ドラッグされたファイル・フォルダのパスを格納します。
        Dim strFileName As String() = CType(e.Data.GetData(DataFormats.FileDrop, False), String())

        'ファイルの存在確認を行い、ある場合にのみ、
        'テキストボックスにパスを表示します。
        '(この処理でフォルダを対象外にしています。)
        If System.IO.File.Exists(strFileName(0).ToString) = True Then
            txt_img.Text = strFileName(0).ToString
            pic_img.SizeMode = PictureBoxSizeMode.Zoom
            pic_img.ImageLocation = strFileName(0).ToString
        End If

    End Sub


問題は、解決してませんが、一応。結果的には同様の事が出来ると思います。
ですが、上記の際に。画像以外のファイルが来た場合の対応が分かりませんね・・・・。
投稿者 KOZ  (社会人) 投稿日時 2021/12/19 11:37:55
ComboBox_Set を通ると発生するのであれば、Connection_DB クラスはどうなんでしょう?
.NET のコンポーネントをそのまま使ってるのであれば、AccessViolationException が起きる可能性は低いと思います。
DllImport で API をコールしているところがあったりしませんか?
投稿者 ロト君  (社会人) 投稿日時 2021/12/19 14:06:50
>ComboBox_Set を通ると発生するのであれば、Connection_DB クラスはどうなんでしょう?
>.NET のコンポーネントをそのまま使ってるのであれば、AccessViolationException が起きる可能性は低>いと思います。
>DllImport で API をコールしているところがあったりしませんか?

Connection_DBは、下記のモノです。




Imports System.Data.OleDb

Public Class Connection_DB

    Public Property Sql = New System.Text.StringBuilder
    Public Property resultDt As New DataTable
    Public Property Connection_Name As String
    Public Property Record_Id As Long

    Sub New()

        Connection_Name = DB_connect
        Record_Id = 0

    End Sub

    Public Sub Selecter()

        'Access接続準備
        Dim command As New OleDbCommand
        Dim da As New OleDbDataAdapter
        Dim cnAccess As OleDbConnection = New OleDbConnection
        cnAccess.ConnectionString = Connection_Name

        'Access接続開始
        cnAccess.Open()

        Try

            command.Connection = cnAccess
            command.CommandText = Sql.ToString
            da.SelectCommand = command

            'SQL実行 結果をデータテーブルに格納
            da.Fill(resultDt)


        Catch ex As Exception
            Throw
        Finally
            command.Dispose()
            da.Dispose()
            cnAccess.Close()
        End Try

    End Sub

    Public Sub Updater()

        'Access接続準備
        Dim command As New OleDbCommand
        Dim da As New OleDbDataAdapter
        Dim cnAccess As OleDbConnection = New OleDbConnection
        cnAccess.ConnectionString = Connection_Name

        'Access接続開始
        cnAccess.Open()

        Dim tran As OleDbTransaction
        tran = cnAccess.BeginTransaction

        Try

            command.Connection = cnAccess
            command.Transaction = tran

            command.CommandText = Sql.ToString
            command.ExecuteNonQuery()

            tran.Commit()

        Catch ex As Exception
            tran.Rollback()
            Throw
        Finally
            command.Dispose()
            cnAccess.Close()
        End Try

    End Sub

End Class
投稿者 KOZ  (社会人) 投稿日時 2021/12/20 00:38:17
ふーむ。OLEDB なのですね。

なんだか気になる記事を見つけました。

「AccessViolationException using System.Data.OleDb #981」
https://github.com/dotnet/runtime/issues/981

Framwork のバージョンを上げてみるってことはできますか?
投稿者 ロト君  (社会人) 投稿日時 2021/12/21 09:42:35
おはようございます。ロト君です。

どうやら、問題は一応解決した感じです。
明確な、解決?ではないかもしれませんが、使ってるアプリケーションの使用してるCPU項目を、AnyCUP自動選択から、x64を選択したら、調子がいい感じになりました!!

それと、VS2019環境で構築していたものを、VS2022にも変更しました!!

皆様、親身になってご相談乗って下さり、ありがとうございました!!
今後も、よろしくお願いいたします。>m<)//
投稿者 ロト君  (社会人) 投稿日時 2021/12/21 09:45:03
参考までに、ここを参照しました。

内容は、異なりますが、AnyCPUについて気になったので。

https://eijiman.com/microsoft-ace-oledb-error/#i-0
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/12/21 11:16:30
> x64を選択したら、調子がいい感じになりました!!
プロバイダーは 32bit 専用 / 64bit 専用 / 両対応なものがありますからね。
参考までに、使用している OLE DB Provider は何だったのでしょうか?



> Connection_DBは、下記のモノです。
> Public Property Sql = New System.Text.StringBuilder

本題からは外れますが、それだと
 Public Property Sql As Object = New System.Text.StringBuilder
の意味になってしまいます。
ローカル変数や引数であれば型推論が働きますが、
フィールド変数やプロパティに対しては適用されません。

そのため、
 Public Property Sql As New System.Text.StringBuilder()
あるいは
 Public ReadOnly Property Sql As New System.Text.StringBuilder()
と書くことをお奨めします。