システムエラー &H800401A8(-2147221080)のお話
たまにはプログラマっぽい話でも。
今の職務上、ゴリゴリ開発するってわけじゃないんですが、VBA組んだりは日常的にしております。Javaに触ることはめっきり減っちゃいましたが。やっててよかったVBA!
んで、以前自分で開発していたツールで、こんなエラーがでて困っていたわけです。
ほげぇぇぇぇ!
H800401A8をダブルクォートで囲って(でないと似たようなエラーコードの記事が引っかかっちゃうからね)検索してみて出てきたStackOverFlowの記事がこちら。
excel - Issues with Multiple Runtime errors: updated code - still having issues - Stack Overflow
どうもWorksheetに触ろうとしたときのエラーだと。
んで、その解決に、「ファイル名をいじった」と。
よくわからぬ…。
なんかうまく再現させることもできなかったので、対症処置で何とかなることもあって放置したんですが、ひょんなことから理由がわかりました。
エラーコードググればわかりますが、あんまり情報もないみたいなので、ここは別にtechblogではないですが、ソースコードを一部公開しておきます。
Dim filePath As String filePath = "C:\hogehoge_NoXXXX.xlsx" ' ファイルオープン時のエラーは処理しない On Error Resume Next Workbooks.Open Filename:=filePath Dim wb As Workbook Set wb = Workbooks("hogehoge_NoXXXX.xlsx") ' ファイルが開けているかチェック If (wb Is Nothing) Then Msg "ファイルが開けていません。" Exit Sub End If Dim ws As WorkSheet Set ws = wb.Worksheet("ぴよぴよ") On Error GoTo 0 ' ここでおちる ws.Select
これは一連のループの中の処理で、NoXXXXには何らかの番号が入り、動的に生成されます。
ほんで、あるものについては問題なくWorksheetをSelect出来るのに、できないことがあった、と。
で、これなぜ起きていたかというと、
- まず、そもそもファイルが存在しない場合がある
- 存在しないファイルをWorkbooks.Openした場合、エラーになるが、On Error Resume Nextしているので、処理自体が無視される
- 開いていないブックをSetしようとすると、エラーになるが、以下略
- 結果、変数wbは、初期化されていない。「初期化されていない変数」は「変数なし」状態であり、これはNothingではない*1
- よって、If(wb Is Nothing) Thenは常にFalseである(エラー処理になっていない)
- 以下wsについても同様
- 変数なしの状態のwsを触ろうとするため、エラーとなる
Javaとか慣れてると、Object obj = new Object();とか呼吸だし、たとえしなかったとしてもnullであるので、IsNullすればいいと思っちゃうけど、VBAでは宣言と初期化は同時に出来ないし、初期化しなかった(ここでは結果的にできていなかった)モノは「変数なし」というよく分からない状況になる*2し、しかもそれはNothingではないし、というようなところで、「あるぇ」ってなってたということのようです。
まあ、VBAでも一応だいたいの値は「ちゃんと存在している値で」Setしますし、でなければCreateObjectしたり、クラスモジュールであればNewしたりしますから、やっぱりこういうのはちょっと意識から漏れたりするのかしら。
なので、修正はこうです。
Dim filePath As String filePath = "C:\hogehoge_NoXXXX.xlsx" '' FIX:ファイル存在チェック If Not (Dir(filePath)) Then Msg "ファイルが存在しません。" Exit Sub End If ' 後略 ---------------------
おさぼりはいけませんねえ、というお話しでした。
しかし、「変数なし(初期化されていない)」変数へのアクセスによるエラー、なんて初心者がやっちまいそうなエラーですが*3、「H800401A8」で出てこなかったのはなんでなんでしょうね。ふしぎ。
ちなみに、IsNothingで「変数なし」を引っかけられないのなら、何だったら引っかけられるんだ?という話なんですが、簡単に調べてみた限りでは引っかける方法が見つからんかったっていう。これはこれで謎です。まあ、変数管理気を付けましょうねというお話しでした。
ご参考:
B.WH |AC : Object型変数の値が <変数なし> となる現象
追記:
うわーなにこれ。なんでこんなことになってんだろう。
一番最初のソースコードは、これはこれであってますね。記事書いてて、これが間違っているなんておかしいと思ったんですよ。
Dim wb As Workbook If (wb Is Nothing) Then Msg "このメッセージは表示されます" End If
つまり、「SetされていないObjectの初期値はNothing」です。コンソールで値ウォッチしていてもそうなっています。こんな記事もあります。
Excel VBAでシートの存在有無を判定するのにループはいらない。On Errorステートメントを使おう - mk_55's diary
しかし、以下には問題があります。
Dim fileIndex As Integer fileIndex = 1 Do LOOP_CONTINUE: Dim wb As Workbook On Error Resume Next Workbooks.Open Filename:="C:\ファイル名_" + fileIndex + ".xlsx" Set wb = Workbooks(filePath) Dim ws As WorkSheet ws = wb.WorkSheet("worksheet") On Error GoTo 0 If (wb Is Nothing) Or (ws Is Nothing) Then ' ファイルを開くのに失敗したり開いたファイルにworksheetワークシートがない wb.Close GoTo LOOP_CONTINUE End If ws.Select ' 問題児コイツ wb.Close ' 開いたworksheetからデータ拾って条件満たしたら終了ってことにしてください If (なにかの条件) Then Exit Do End If fileIndex = fileIndex + 1 Loop
このループの初回、変数wbもwsも、初期値はNothingです。なので、Is Nothingで問題なくチェックできます。
が、「問題児コイツ」と書いたwb.Closeを実行した時点で、wbもwsも「変数なし」となり、Loop先頭に戻った時点でもNothingになりません。
これが今回の根本原因でした。
ということで、「初期値をSetしない値のチェックについて、Is Nothingについては、ループ構文内で使わないならば、問題ない」が、「ループ内で同じ変数を使いまわすのであれば要注意」です*4。
なので、こうするのが良さそう。
Dim wb As Workbook, ws As WorkSheet Dim fileIndex As Integer fileIndex = 1 Do LOOP_CONTINUE: Set wb = Nothing Set ws = Nothing On Error Resume Next Workbooks.Open Filename:="C:\ファイル名_" + fileIndex + ".xlsx" Set wb = Workbooks(filePath) ws = wb.WorkSheet("worksheet") On Error GoTo 0 If (wb Is Nothing) Or (ws Is Nothing) Then ' ファイルを開くのに失敗したり開いたファイルにworksheetワークシートがない wb.Close GoTo LOOP_CONTINUE End If ws.Select wb.Close ' 開いたworksheetからデータ拾って条件満たしたら終了ってことにしてください If (なにかの条件) Then Exit Do End If fileIndex = fileIndex + 1 Loop
なんでこんな仕様なんだ…。
以上追記まで。