[[20161026073651]] 『ThisWorkbookのMeについて』(ラビット) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『ThisWorkbookのMeについて』(ラビット)

お世話になります。
「VBAで、ThisWorkbookクラスモジュール内で、
Meを頭につける場合と、付けない場合のエラー処理結果の変化について」
原因がわからず、ご存知の方がいらっしゃったらご教示頂きたく思います。

処理概要としては、ExcelからAccessのaccdbファイルに、
ADOを用いて接続するものなのですが、
エラーのコールスタックの際に、下記のように、
Meに付ける場合と付けない場合(省略した場合)で、
Err.Descriptionの値が変わってしまいます。
(付けない場合が想定ケース)
結果としては、想定ケースも得られるので良いですが、
原因が分からないため、逆のパターンもあったり、
バージョン、環境によって異なるのではと思いました。
余計な部分は省略するため、ソースコードは変更してあります。

' *ThisWorkbookクラスモジュール

Private Sub Test_CallStack()

Dim shouldStop As Boolean

On Error GoTo Error

    Call OpenDb
    ' 上記の場合(Meを付けない場合)
    ' エラー番号    : -2147467259
    ' エラー内容    : ファイル 'C:\XXX\DB.accdb' が見つかりませんでした。
WithOutMe:
    Let shouldStop = True
    Call Me.OpenDb
    ' 上記の場合(Meを付ける場合)
    ' エラー番号    : -2147467259
    ' エラー内容    : オートメーション エラーです。エラーを特定できません
    Exit Sub
Error:
    Debug.Print CStr(Err.Number) & ":" & Err.Description
    If shouldStop Then Exit Sub
    Resume WithOutMe

End Sub

' DB接続処理
Public Sub OpenDb()

On Error GoTo Error

    With CreateObject("ADODB.Connection")
        .Open "Provider=Microsoft.ACE.OLEDB.12.0;" _
            & "Data Source=C:\XXX\DB.accdb"
    End With
    Exit Sub
Error:
    Err.Raise Err.Number, , Err.Description

End Sub

思い当たる方がいらっしゃったら、
コメントを宜しくお願い致します。

< 使用 アプリ:Excel2007 & Excel2010、使用 OS:Windows Vista & Windows 7 >


こんにちは

meの出すOpenDbが失敗したというエラーか
OpenDb内のCreateObject("ADODB.Connection").Openの出すエラーか
の違いでは?

Dim m As Variant
Private Sub Test_CallStack()

    Dim shouldStop As Boolean
    On Error GoTo Error
    Call Me.OpenDb
    ' 上記の場合(Meを付ける場合)
    ' エラー番号    : -2147467259
    ' エラー内容    : ファイル 'C:\XXX\DB.accdb' が見つかりませんでした。
WithOutMe:
    Let shouldStop = True
    Call OpenDb
    ' 上記の場合(Meを付けない場合)
    ' エラー番号    : -2147467259
    ' エラー内容    : ファイル 'C:\XXX\DB.accdb' が見つかりませんでした。
    Exit Sub
Error:
    Debug.Print m   'CStr(Err.Number) & ":" & Err.Description
    If shouldStop Then Exit Sub
    Resume WithOutMe
End Sub

' DB接続処理
Public Sub OpenDb()
On Error GoTo Error

    With CreateObject("ADODB.Connection")
        .Open "Provider=Microsoft.ACE.OLEDB.12.0;" _
            & "Data Source=C:\XXX\DB.accdb"
    End With
    Exit Sub
Error:
    m = Err.Number & Err.Description
    Err.Raise Err.Number, , Err.Description
End Sub

とすれば、どちらもOpenDbの出すエラー内容を表示します。

(ウッシ) 2016/10/26(水) 08:35


アップされたコードの形で実行されたのですね?

 当方、ADODB 含めて、DBハンドリング、全くの素人ですが、2つの Call 、 Me がついている、ついていないというほかに
 1つめは ファイルがないというエラーが発生、それなのに 2つめでは それを また参照しようとしていますよね。
 つまり、2つの Call は 実行時点の状況が異なるわけですよね。

 Me をつけたから、どうこう ということではないと思います。

 最初の Call に Me を付けるとどうなりますか?
 Me を付けないものと同じエラー、つまり ファイルが見つからない ということになると思うのですが。

 想像ですけど、1つめの ファイルNFD の時点で、何かしら、その参照ポインタのようなオブジェクトが出来上がっている。
 ファイルがないわけですから、その中身は正しくないわけですけど、2つめの処理では、参照ポインタが存在するので
 それを見に行こうとする。参照ポインタを頼りにオブジェクトアクセスをしようとしたら、たどり着けない。
 なので、オートメーションエラーだと、これは ADODB に限らず、まま 発生する事象なので。

 素人の想像です。間違っているかもしれません。

(β) 2016/10/26(水) 08:39


エラー割り込み処理中に、Err.Raiseで更にエラー割り込みを発生させており、飛んだ数とResumeする数が合わなくなりますよね(エラートラップから戻すのではなく、更にエラーを発生させて、別のエラートラップに飛んでいる)。これがオートメーションエラーと表示されている理由ではないでしょうか。美しくない方法であるのは確かかと。推測でしかないですが、Meを付けない場合、リソース開放を自動で行うような感じで、Excelが自動的に都合付けてくれているのかと思います。

で、On Error によるトラップがネストし、おかしな事になっているのだから、プロシジャ内で発生したエラーを呼び元で得たいならば、以下の様にしてはいかがでしょうか。(または、判定用にiErrだけ返して、OpenDb内でエラー表示させてしまっても良いかと)

 Private Sub Test_CallStack()
    Dim shouldStop As Boolean
    Dim iErr As Long
    Dim cErr As String

    Call OpenDb(iErr, cErr)
    If iErr <> 0 Then
        Debug.Print iErr & ":" & cErr
    End If

    Let shouldStop = True
    Call Me.OpenDb(iErr, cErr)
    If iErr <> 0 Then
        Debug.Print iErr & ":" & cErr
    End If
 End Sub

 'DB接続処理
 Public Sub OpenDb(iErr As Long, cErr As String)
    iErr = 0
    cErr = ""

    Err.Clear
    On Error GoTo sError
    With CreateObject("ADODB.Connection")
        .Open "Provider=Microsoft.ACE.OLEDB.12.0;" _
            & "Data Source=C:\XXX\DB.accdb"
    End With

 sExit:
    On Error GoTo 0
    Exit Sub

 sError:
    iErr = Err.Number
    cErr = Err.Description
    Resume sExit
 End Sub
(???) 2016/10/26(水) 09:52

皆さま、ご回答頂きありがとうございます。
当方の仕事が終わったら、また丁寧に書きますが、
取り急ぎ、補足致します。

ここに載せたものでも、同様の結果を確認済みですが、
元々は、クラスモジュールにデータベース関連処理を閉じ込め、
そこの中で、メンバ変数として、
Private myConnection As ADODB.Connection
Private myRecordset As ADODB.Recordset
と、ADO(2.1)の参照設定済みで、宣言してあります。
DBの接続処理(OpenDb)も、メソッドとしてPublicで宣言してあります。

また、両方を1度に確認できるように、あえて、Resume 〜という
書き方をしましたが、どちらかをコメントアウトして
片一方だけで実行というのも検証済みで、同じ結果でした。

省いたつもりが、そのせいで別の要因を考えさせてしまい
申し訳ありませんでした。

>ウッシさん
ありがとうございます。
モジュール変数、グローバル変数でする方法、同じ結果でした。
こちらの方が確実な気もします。

βさん ありがとうございます。
そういう所まで考えておりませんでした。(浅いですね..)

???さん ありがとうございます。
一見、参照渡しでしょうかこれは。
時間が来てしまったので、仕事終わりにきちんとみて回答致します。
美しくない方法であるのは確か こちらについてもお話させて下さい。
(ラビット) 2016/10/26(水) 14:15

再度、補足致します。

どうも、Meを付けた場合と付けない場合で挙動が違う気がします。
ウッシさんのおっしゃる、Meの出す呼び出し自体のエラーと、
Meを付けない場合は、呼び出し先のエラー内容が返ってくるということでしょうか?
これは初耳です。

以下のサンプルでもエラー内容は異なりました。

Private Sub Test_DivisionZero()

Dim shouldStop As Boolean

On Error GoTo Error

    Call Me.DivisionByZero
    ' 上記の場合
    ' エラー番号    : 11
    ' エラー内容    : 0 で除算しました。
WithOutMe:
    shouldStop = True
    Call DivisionByZero
    ' 上記の場合
    ' エラー番号    : 11
    ' エラー内容    : 0で割られた!!!
    Exit Sub
Error:
    Debug.Print CStr(Err.Number) & ":" & Err.Description
    If shouldStop Then Exit Sub
    Resume WithOutMe

End Sub

Public Sub DivisionByZero()

Dim num As Long

On Error GoTo Error

    num = 1 / 0
    Exit Sub
Error:
    Err.Raise Err.Number, , "0で割られた!!!"

End Sub

また、最初に提示したソースコードの内容の件ですが、
今まで(といっても数か月すらありませんが...)、
Excel VBAを勉強してきて、
Public変数や参照渡しに関する否定的な意見を見てきました。
「変数のスコープは出来る限り、狭くした方が良い」
「I/Oを正しく追わないと予期せぬバグが云々...」

今回のものも、一つのプロシージャに収めれば、
On Error GoTo 〜も一つで済むのですが、
共通処理などは、別プロシージャに分けて管理したくなります。

趣味の範囲内ですが、やはり「上手い書き方」は
身に着けたいと思っております。
(その意味でこちらの"学校"は勉強になります)

複数のプロシージャにまたがるような場合として、
エラー処理は下記を参考にしておりました。
(h ttp://shuhho.hatenablog.com/entry/excelvba-102)
(h ttp://kazu-s-diary-2.cocolog-nifty.com/blog/2012/06/vbaerr-1d80.html)

上記のようなエラートラップはアブノーマルなのでしょうか。
(h ttp://yamav102.cocolog-nifty.com/blog/2015/05/post-8a80.html)
のように、最初にだけ記述する方法もありかなとは思っていますが、
皆さんの意見をお伺いしたいです。

(ラビット) 2016/10/26(水) 20:57


 エラートラップは適切な場所に仕掛けて有効に使う。 これが大原則だと思っています。
 仕掛ける範囲は、
 ・回避できないエラー、
 ・もしくは、回避しようとすると、多くのコードが必要で面倒
 ・もしくは、多くのコードで回避しようとしても完全には回避できない
 ということが明白にわかるものに対して、範囲を絞って仕掛けるべきだと思っています。

 そうではなく、自分の書いたコードにバグがあるかもしれない、だからエラートラップを掛けようというのは
 「論外」であって、その極端な形、プロシジャの冒頭に On Error なんたら と書いておけば安心だねというのは
 もう、「邪道」だと思っています。

(β) 2016/10/26(水) 21:54


βさん

確かに、
サンプルコードでいうと、
0除算は割られる数が0かどうか事前にチェックすればよいですし、
データベースに接続するときもFileSystemObjectを使えば、簡単に事前に存在確認は出来ますね。
無闇には使用しないという方針で書いていこうと思います。

それと、
ThisWorkbook 内のMeの件は、
SheetやThisWorkbook 内の関数は、
他のモジュールから呼び出せないのだから、
イベントプロシージャしか書かない。普通は標準モジュールに書く
ということにして、他の勉強にあてます。

(ラビット) 2016/10/27(木) 07:27


 >>イベントプロシージャしか書かない。普通は標準モジュールに書く 

 βも、オブジェクトモジュールにはイベント処理と、それに密接に関連するコードを書き、
 ふつうは 標準モジュールに書きます。

 でも、これは 人それぞれで、上級者さんは、わりあいと、オブジェクトモジュールを主として使い
 むしろ、標準モジュールは 例外 として使うかたが多いように思えます。

 >>SheetやThisWorkbook 内の関数は、 
 >>他のモジュールから呼び出せないのだから、 

 呼び出せないことはありません。

 以下のコードで、Test を実行してみてください。

 ThisWorkbookモジュール

 Sub wkbProc()
    MsgBox "WorkBook"
 End Sub

 シートモジュール ("Sheet1"のシートモジュール)

 Sub shtProc()
    MsgBox "Sheet"
 End Sub

 標準モジュール

 Sub Test()
    ThisWorkbook.wkbProc
    Sheets("Sheet1").shtProc
 End Sub

(β) 2016/10/27(木) 08:33


βさん

>ThisWorkbook.wkbProc
>Sheets("Sheet1").shtPric

こんな書き方も出来るんですね。(違和感はありますが)
誤って認識していました。失礼致しました。

これはPublicにしても、ちゃんと
頭にモジュール名をつけないとダメなようですね。

ありがとうございました。
この件はクローズ致します。

(ラビット) 2016/10/27(木) 17:35


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.