質問者
Interop.Excelを使用したSaveAsメソッド実行によりエクセルがクラッシュする

質問
-
Windows環境1607 ビルド14393.222
Excel環境15.0.4859.1002
.Net環境 4.6
参照設定
Microsoft.Office.Interop.Excel 14.0.0.0
以下のソースを実行すると、エクセルが動作を停止しましたとメッセージを出してきます(例外コード0xc0000005)。
エクセルが停止しているため、xlAppに対してのロジックで例外が発生してしまいます。
どこか問題はありますでしょうか?
SaveAsメソッドを呼び出さないようにするとエクセルは異常終了せずに完了することができます。
補足ですが、ハードウェアグラフィックアクセラレータはエクセル上で無効化しています。
TESTPATHは保存先のパスです。
private void Test1() { Excel.Application xlApp = new Excel.Application(); try { xlApp.Visible = false; xlApp.DisplayAlerts = false; xlApp.ScreenUpdating = false; xlApp.EnableEvents = false; Excel.Workbooks xlBooks = null; try { xlBooks = xlApp.Workbooks; for (int i = 0; i < 10; i++) { Excel.Workbook xlBook = null; try { xlBook = xlBooks.Open(TESTPATH + "Template.xls"); } finally { string savePath = String.Format(@"{0}{1}.xls", TESTPATH, "test" + i.ToString()); try { //保存 SaveBook(xlBook, savePath, xlApp.Version.ToDouble()); } finally { xlBook.Close(false); Marshal.ReleaseComObject(xlBook); xlBook = null; } } } } finally { xlBooks.Close(); Marshal.ReleaseComObject(xlBooks); xlBooks = null; } } finally { // Excel を終了する xlApp.ScreenUpdating = true; xlApp.EnableEvents = true; xlApp.DisplayAlerts = true; xlApp.Quit(); Marshal.ReleaseComObject(xlApp); xlApp = null; } MessageBox.Show("処理を完了しました"); } /// <summary>エクセルブック保存処理 /// </summary> /// <param name="xlBook"></param> /// <param name="savePath"></param> /// <param name="version"></param> private static void SaveBook(Excel.Workbook xlBook, string savePath, double version) { //保存 if (version < 12) { xlBook.SaveAs(savePath); } else { xlBook.CheckCompatibility = false; xlBook.SaveAs(savePath, Excel.XlFileFormat.xlExcel8); } return; }
すべての返信
-
直接的な回答ではないですが、
Excelの新しいのがないので自分の環境で動かすことはできませんが、
保存時に、ということなら
・TESTPATHとファイル名をくっつけたときにおかしいことになっていないかとか、
・何かダイアログが出ているとか確認するために xlApp.DisplayAlerts=true;にしてみる、
とかから確認すると思います。
あと、 xlBook.CheckCompatibility = false; を
保存前ではなくブックを開いた直後にしたらどうなるでしょうか。
直前だとダイアログが抑制できないことがあるのを聞いたことがある気がします。 -
> xlApp.Visible = false;
本題とは関係無いですが、検証段階では、Visible = true の方が状況が把握しやすいかと思います。それに非表示にすると、バックグラウンド動作となって処理速度が落ちるケースもありますし。
> SaveBook(xlBook, savePath, xlApp.Version.ToDouble());
ToDouble というのは、String に対する拡張メソッドでしょうか。
Version プロパティは数字以外、たとえば "8.0n" や "8 z" のような文字列を返すこともあるのでご注意ください。> どこか問題はありますでしょうか?
コードに問題箇所は思い当たりませんでした。保存先フォルダへの書き込みアクセス権が不足しているわけでも無いのですよね?
とりあえず当方環境で問題は生じませんでしたが、余裕があれば、別の PC でも試してみます。それと、デバッグ段階で Excel のゾンビプロセスが残って不安定になっているケースもありますので、一度 OS を再起動してみるのは如何でしょう。
SaveAs はバージョンの差が大きい箇所ではありますが、Excel 15 のタイプライブラリで Excel 15 を動かす分には関係ないはず…。
if (version < 8D) { ((dynamic)xlBook).SaveAs(savePath); } else if (version < 10D) { xlBook._SaveAs(savePath); } else if (version < 12D) { xlBook.SaveAs(savePath); } else { xlBook.CheckCompatibility = false; xlBook.SaveAs(savePath, Excel.XlFileFormat.xlExcel8); }
-
とりあえず、WER が APPCRASH を検出したらプロセス ダンプを生成する設定をして、現象発生時のダンプを採取して、それを公開してみては?-------------------------------------------
Windows7のWERでアプリケーションのクラッシュダンプを取得する方法
http://d.hatena.ne.jp/replication/20140427/1398575903
-------------------------------------------
(ただしダンプ ファイルには、ログオン アカウント名やホスト名などが含まれるので、それらを個人情報と考えるのであれば、ダンプ ファイルの公開はお勧めしません。) -
魔界の仮面弁士さん。回答ありがとうございます。
xlApp.Visible = true;に変更して実行して見ましたが、
再現してしまいました。
上記検証時にわかったのですが、
xlApp.ScreenUpdating = true;
がに他の部分を変更せずに、上記をtrueにして実行すると、
最後までクラッシュせずに実行することができました。
しかし、更に処理(シート内の編集)を追加した場合は結局クラッシュしてしまいました。
>ToDouble というのは、String に対する拡張メソッドでしょうか。
そうです、拡張メソッドになっています。
文字列が混入した場合は、0がとれるため、
一応問題なく動くようです。(現状では15.0が取得できます)
public static double ToDouble(this String str) { return IsNumeric(str) ? double.Parse(str) : 0; } public static Boolean IsNumeric(this String str) { double dNullable; return double.TryParse( str, System.Globalization.NumberStyles.Any, System.Globalization.NumberFormatInfo.InvariantInfo, out dNullable ); }
SaveBookメソッドの参考にさせて頂きます。
ありがとうございます。
- 編集済み issei1102 2016年10月4日 5:17
-
> ダンプを公開は大丈夫なのか判断でき無いのですが、
> 大丈夫なものなのでしょうか?
何かあったときに、私には責任を負いかねますので、ご自身でご判断ください。
> EXCEL.EXE の 0x00007ff6a637ce94 でハンドルされていない例外が発生しました:
> 0xC0000005: 場所 0x0000000000000078 を読み込み中にアクセス違反が発生しました。
0xC0000005 は STATUS_ACCESS_VIOLATION というエラーで、要は「アクセス違反」を示しています。
「場所 0x0000000000000078」というのが、無効なアドレスを示しているんだと思います。
デバッガ上で確認しているのであれば、クラッシュ時のコール スタックと "!peb" コマンドの出力結果を提示していただけますか?
(SaveAs メソッド呼び出すと問題が起きる。。。とのことから、もしかしたらウィルス対策ソフトが影響しているのかも。。。) -
お馬鹿さん。
ダンプの公開は難しそうです。
>デバッガ上で確認しているのであれば、クラッシュ時のコール スタックと "!peb" コマンドの出力結果を提示していただけますか?
visualstudioがたまたま拾えただけなようで、きちんとしたデバッガとしては機能しないため、
コールスタックは取得できませんでした。
また、pebコマンドを当方は存じておりません、どうすれば取得できますでしょうか?
ウイルス対策ソフトはESETのスマートセキュリティーを使用しています。
OFFにして再度確認してみましたが、結果は変わりませんでした。
EXCELのクラッシュですので、イベントビュアーに毎回ログが残っていました。
障害が発生しているアプリケーション名: EXCEL.EXE、バージョン: 15.0.4859.1000、タイム スタンプ: 0x57b2cf15
障害が発生しているモジュール名: ntdll.dll、バージョン: 10.0.14393.206、タイム スタンプ: 0x57dac931
例外コード: 0xc0000005
障害オフセット: 0x0000000000048594
障害が発生しているプロセス ID: 0x1f84
障害が発生しているアプリケーションの開始時刻: 0x01d21e02011a72e3
障害が発生しているアプリケーション パス: C:\Program Files\Microsoft Office 15\Root\Office15\EXCEL.EXE
障害が発生しているモジュール パス: C:\WINDOWS\SYSTEM32\ntdll.dll
レポート ID: 4f6a3510-7369-4037-be8a-c91e0c22e50c
障害が発生しているパッケージの完全な名前:
障害が発生しているパッケージに関連するアプリケーション ID: -
> ダンプの公開は難しそうです。
だったら、先に示した方法で APPCRASH 時のプロセス ダンプを採取し、Visual Studio か WinDBG でそのダンプ ファイルを開き、自分で解析すればよいのでは?
(Visual Studio は重たいので、私はプログラムのビルド以外のライブ デバッグおよびダンプ解析は、すべて WinDBG で行っています。)WinDBG の場合、シンボル パスを設定した状態で採取したプロセス ダンプを開き、下記コマンドを実行すれば、コール スタックや PEB (Process Environment Block) の情報を確認できます。
(WinDBG 上できちんとシンボル パスを設定しないと、下記コマンドの結果はあてになりません。)-----------------------------------
<WinDBG でとりあえず実行するコマンド>
!analyze -v
~*kvn
!peb
-----------------------------------なお、上記コマンドだけで原因を特定できる。。。という意味ではありませんので、あしからず。
(最低限、まずは上記コマンドの結果から、問題となっていそうな部分のあたりを付ける。。。ということです。) -
複製のテンプレートとして使うのなら、xlBooks.Open ではなく xlBooks.Add の方が意図に近い気も。
> 最後までクラッシュせずに実行することができました。
Template.xls 固有の問題かもしれませんね。空のワークブックをテンプレートにした場合もクラッシュするのでしょうか。現象を再現できるワークブックがあればご提供いただけるとありがたいです。
> 文字列が混入した場合は、0がとれるため、
たとえば Excel 97 の場合、Service Release 1 だと "8.0a" 、Service Relase 2 では "8.0e" といった文字列が返されます(パッチの適用状況によっても変わります)。このような場合、double に変換するなら、8.0 の方がバージョン判定しやすいのではないでしょうか。
そのため、バージョン判定に使う場合は、Excel VBA でいうところの「Val(Application.Version)」相当の実装にすることをおすすめしています。
それと、変換できない場合は 0 にしているとのことですが、そのロジックにも抜けがあるように見えます。
たとえば「var d = "(123)".ToDouble();」は FormatException の例外になってしまいますが、この場合の期待値は 0 (あるいは -123 か 123)ですよね。
// 修正案1 // double.Parse に、IsNumeric で指定した書式と同じ物を渡します。 public static double ToDouble(this String str) { return IsNumeric(str) ? double.Parse(str, NumberStyles.Any, NumberFormatInfo.InvariantInfo) : 0D; } // 修正案2 // Double.TryParse が false を返す場合、out パラメーターは // default(double) すなわち 0.0 になる仕様です。 public static double ToDouble(this String str) { double dbl; double.TryParse(str, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out dbl); return dbl; }