質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

1回答

5469閲覧

C# Excelへの転記後、Excelを閉じずにオブジェクトの解放は可能?

a.fuku

総合スコア8

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2016/11/22 00:49

編集2022/01/12 10:55

###前提・実現したいこと
C#のプログラム(フォーム)から、起動中のExcelにDBより取得した値を転記する。
転記後もExcelは開いたままで、ユーザーのEXCEL操作で保存や終了をしたい。

###発生している問題・エラーメッセージ
転記処理自体は上手くいくのですが、転記後にエクセルを閉じる際に、
VBAプロジェクトパスワードを確認する画面が、5回表示されてしまいます。
このパスワード確認画面を表示しないようにしたいです。
パスワード未入力で5回Okを押すと、閉じることができます。

###該当のソースコード

private static bool CheckTransExportDebug( Excel.Application xlApp, Excel.Workbook xlBook, DataTable transData, ref bool warnFlg, ref List<string> warnList, ref int regCount ) { Excel.Worksheet xlSheet = null; Excel.Range targetCells = null; Excel.Range targetRange = null; try { // 作業シート読み込み xlSheet = (Excel.Worksheet)( xlBook.Sheets[SHEET_EXPORT] ); // セルへ書き込み targetCells = xlSheet.Cells; targetRange = (Excel.Range)targetCells[15, 12]; targetRange.Value2 = "10"; return true; } catch ( Exception ex ) { logger.Exception( ex ); return false; } finally { // Excelリソース解放 Marshal.ReleaseComObject( targetRange ); Marshal.ReleaseComObject( targetCells ); Marshal.ReleaseComObject( xlSheet ); Marshal.ReleaseComObject( xlBook ); Marshal.ReleaseComObject( xlApp ); } }

###試したこと
転記後、C#のアプリを先に閉じた場合、エクセルを閉じてもパスワード確認画面は出ませんでした。

EXCELリソース解放処理の前に、上記ではコメント中ですがEXCELを閉じる処理を入れてみたところパスワード確認画面は立ち上がりませんでした。
xlBook.Close();
xlApp.Quit();

このことから、C#でエクセルオブジェクトが解放されていないのが原因という可能性が高いと思っているのですが、そもそもエクセルを閉じずにオブジェクトを解放することはできるのでしょうか?

###補足情報(言語/FW/ツール等のバージョンなど)
(環境)
Windows7Professional
C# VisualStudio2012
.NetFramework4.0
Oracle 11g
Excel2010
Excelへのアクセスは、Interopを使用。

(Excelの制限)
Excelは他システムベンダーが開発したもので、シート保護されている。
許可された項目以外は入力できない。
もちろんVBAも修正できない。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Y.H.

2016/11/22 05:32

プログラムコードは ```ではさんでコードブロックにしていただけると見やすくなります。
a.fuku

2016/11/24 09:15

ご指摘ありがとうございます。コードの最初と最後にシングルクォーテーションをつけてみたのですが、まだうまくブロック化できていないように思います…操作が間違っているでしょうか?初めて利用したのですみません。
Y.H.

2016/11/24 09:18

シングルクォーテーションではなく「```」の単独行ではさんでください。
a.fuku

2016/11/24 09:23

ブロック化できました。ご教授頂きありがとうございます。
guest

回答1

0

内容がほぼ同じなので以下の質問をされた方と同じ方でしょうか・・・もしそうでしたらteratailのアカウントを変えずに質問していただくと質問履歴を辿りやすく回答しやすいので変えずにいただけると助かります。(同じ業務をやっておられる別の方かもしれませんが・・・)

C#からExcel操作 Excelを閉じる際にVBAプロジェクトパスワード確認画面が表示されてしまう

上の回答には「リリース漏れ問題を撲滅する必要がある」と書きましたが、逆を言えば「リリース問題さえ無くせばオブジェクトは解放できるはず」と思います。


追記:2016/11/28 21:17
気づかずにリリース漏れしていることを客観的に確認しやすい方法が一つ考えられます。面倒なのですが調査が難航しているなら(かつ充分問題がしぼりこめているなら)やってみる価値があるかも知れません。

その方法とはExcelに対する全ての操作をC#とまったく同じことをするようなVBAで記述し、その動作を確認することです。その際Excelの全てのCOMオブジェクトはテストコードのサブルーチンのローカル変数にし、グローバル変数には一切COMオブジェクトを格納しないことです。このようにして作成したテストサブルーチンを実行してサブルーチンから呼び出し元へ戻ると全てのCOMオブジェクトは(VBAの自動リリース機能によって)完全にリリースされます。その場合に現象がおきないのであればC#プログラムでのリリース漏れの可能性が高いと思います。逆にVBAのテストサブルーチンを実行しても同様の現象になるのであればリリース漏れの可能性は低いと思います。


追記:2016/11/29 00:01
コードを絞り込まれたようなのでそれをベースに自分もC#でCOM操作のコードを書いてみました。以下説明文に(RC=?)と書いている部分がありますが参照カウンターのことと思ってください。

  1. リファレンスカウンターの確認方法

完全ではないかも知れませんが、Marshal.ReleaseComObjectの戻り値がリリース後のCOMオブジェクトの参照カウンターを表しています。これを利用し、リリース後の参照カウンターが0になるかどうかを確認するというデバッグが可能のようです。このリファレンスカウンターがサーバー側にある本当のカウンターかどうかははっきりわかりませんが、いずれにせよそれが0になるかを確認することは有効なデバッグになると思います。
下の方に「リリースし、かつ結果の参照カウンターをデバッグプリントする」ユーティリティーメソッドReleaseComを書いておきます。

  1. finally節のバグ

質問コードにあるfinally節の処理に問題があると思います。Marshal.ReleaseComObjectにnullを渡すと例外が発生するからです。try本体で何かの例外が発生したり、処理のルートによって一部のCOMオブジェクト格納用の変数がnullのままになっているとfinally節の途中で例外が発生し、それ以降のリリースが行われないのでリリース漏れの危険があります。
これを回避するには下の方に書いたReleaseComメソッドのようにnullを渡したときに余計なことをしないユーティリティーメソッドを用いると簡単です。

  1. バグがどうかはっきりわからなかったもの

Book.Sheets[index]というコードはリリース漏れの原因になっているような気がしましたがはっきりしません。このコードを実行するとExcel側では次のことが起こるはずです。
(1)WorkbookのSheetsプロパティーを返す。結果はWorksheetsオブジェクト(RC=1)
(2)Worksheetsのitemメソッドを実行。結果はWorksheetオブジェクト(RC=1)
質問のコードでは(1)をリリースしていないように見えます。でも実際に実行してみると(1)をリリースしてもしなくても違いが見えませんでした。ここはどういう仕組みになっているのか自分にもわかりませんが、自分なら安全側に考えて、
sheets = Book.Sheets;
sheet = sheets[index];
...
ReleaseCom(sheet, "Sheet");
ReleaseCom(sheets, "Sheets");
と書きます。(自信なし)

  1. リリースの場所

質問のコードではメソッドの引数に渡されるxlApp, xlBookをメソッドの中でリリースしています。調べてみるとこれは奇妙な実装に見えてきました。COMオブジェクトを引数に渡しても参照カウンターは上がりません。ゆえにこのメソッドでリリースし、呼び出し元でもリリースすると「リリース過多」となりRC=-1になってしまいます。MSDNをざっと読むとRC=-1のオブジェクトに対して何かのメソッド呼び出しやプロパティー参照をするとunmanagedコードの中で危険なことが起こるように見えました。
よってリリース対象は「そのメソッドの中でプロパティー参照やメソッド参照を通じて新たに取得したCOMオブジェクトだけ」にするのが適切だと思います。

C#

1Excel.WorkBook xlBook = ...; 2... 3ReleaseCom(xlBook, "book"); //<=このように使う 4 5... 6public static void ReleaseCom(object o, string msg) { 7 if (o != null) { 8 int rc = Marshal.ReleaseComObject(o); 9 log("released rc={0} {1}", rc, msg); 10 } else { 11 log("not released {0}", msg); 12 } 13} 14 15public static void log(string format, params object[] args) { 16 Debug.WriteLine(string.Format(format, args)); 17}

投稿2016/11/22 03:48

編集2016/11/28 16:16
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

a.fuku

2016/11/22 04:03

ややこしくて申し訳ありません。 過去の質問は、同じプロジェクトメンバーの質問でした。 一旦解決ということでクローズしていたようなのですが、その後解決していないことがわかり、 私の方で改めて質問させていただきました。 >上の回答には「リリース漏れ問題を撲滅する必要がある」と書きましたが、逆を言えば「リリー>ス問題さえ無くせばオブジェクトは解放できるはず」と思います。 エクセルのリリースについていろいろ調べてみたのですが、基本的にブックのクローズ、エクセルアプリの終了処理が前提になっており、そもそもエクセルを開いたままオブジェクトの解放ができるのかどうか疑問に思い質問させて頂きました。 当プログラムにおいても、エクセル終了処理を入れればオブジェクトの解放は問題なくできました。
KSwordOfHaste

2016/11/22 05:20

わかりにくくてすみません。自分がいう「リリース」とはCOMオブジェクトのリファレンスカウンターをデクリメントすることで、Excelなどのbookやsheet等々などをclose/deleteするといった意味はないのです。Excelに対して「もうこのbookは使い終わりました」と伝えることといえばよいでしょうか。リリース漏れがまずいのは使わなくなったのに「使い終わりました」と言い忘れることでCOM提供側(Excel)が「まだこのbook使われてる・・・」と勘違いしてしまうことです。この不整合状態は様々な問題を引き起こし、メニューから終了した際に画面は消えるがCOMオブジェクトが残ったままの状態になる等々の不具合が発生します。
KSwordOfHaste

2016/11/22 05:29

誤解を恐れるので申し上げます。現在の状況はリリース漏れが原因かどうか自分には確信はありません。ただ自分はダイアログが何度も意味なく出てくるという現象がExcelに何かの不整合が発生していると推測したのでありそうな原因の一つとしてリリース漏れではないかと申し上げたかったのです。説明が下手ですみません。
a.fuku

2016/11/24 09:21

返信が遅くなり申し訳ありません。 COMオブジェクトのリファレンスカウンターをデクリメントする処理が上手くできていない可能性が高く、その処理はエクセルをクローズとは関係なく可能ということですね。 引き続きその方向で調査してみます。情報提供いただき、ありがとうございます。
KSwordOfHaste

2016/11/24 10:32

> クローズとは関係なく可能ということ はい、おっしゃるとおりです。
a.fuku

2016/11/28 10:32

原因を絞り込む為、1つのセルに値をセットする処理だけ残してソースを簡略化して試してみたのですが、このソースでもやはり解決しませんでした。 もちろん、セルに値をセットする処理をコメントアウトすればパスワードの入力画面は出てこなくなります。このソースでもリリース漏れの可能性は考えられるのでしょうか…
KSwordOfHaste

2016/11/28 12:10

処理をみてみないと不用意なことはいいかねます。リリース漏れはコードをつぶさに見ないと気づけないことが多いため言葉だけでは推定・指摘が難しいです。あまりよい検証方法ではないですが一つの手段を追記してみます。
KSwordOfHaste

2016/11/28 16:17 編集

失礼しました。質問文のコードが再現コードですか?そうならちょっと試してみます。 =>試してみて問題かも知れない点とデバッグ方法について追記しました。
a.fuku

2016/11/29 05:03

ご丁寧にご教授頂き、誠にありがとうございます。 結論から申し上げますと、まだ解決していないのですが、 以下にご指摘頂いた内容について、対応した内容を記載しました。 まず、Excelに対する全ての操作をC#とまったく同じことをするようなVBAで記述するという方法については、まだ試せていないのですが、 転記後、C#のアプリをExcelよりも先に終了した場合パスワード入力画面が出ないことから、 C#からExcelオブジェクトのリリース漏れの可能性が高いと判断していました。 1.リファレンスカウンターの確認方法 ご教授頂き、ありがとうございます。 実装してログを確認した結果、リリース後の参照カウンターが0になっている事が確認できました。 2.finally節のバグ ご指摘の通りです。元コードではnull判断をメソッドで行っていたのですが、 今のコードはnull判断が漏れていました。 記載して頂いたようにメソッドでnull判断をするよう修正しました。 3.バグがどうかはっきりわからなかったもの sheet = sheets[index]; よくわからなかったのですが、 この書き方ができないというエラーになってしまったので、とりあえずそのままにしてあります。 4.リリースの場所 ご指摘の通り、呼び出し元でもリリースを行った場合RC=-1になってしまいましたので、 呼び出し元でのみリリースを行うように変更しました。 上記の対応後、一度解決したと思われたのですが、 その後の調査で、パスワード入力画面が出る時と出ない時があることがわかりました。 コードも操作も全く同じなのですが… オブジェクトのリリース前に、 System.Threading.Thread.Sleep( 1000 ); で1秒間止めてみたり、リリース後に GC.Collect(); を入れてみたりいろいろ試しているのですが、結果はやはり出るときと出ない時があるようです。
KSwordOfHaste

2016/11/29 06:12

3.については以下のように変更するイメージです。 xlSheet = (Excel.Worksheet)( xlBook.Sheets[SHEET_EXPORT] ); => Excel.Sheets xlSheets = null; ... xlSheets = xlBook.Sheets; xlSheet = xlSheets[SHEET_EXPORT]; ... }finally{ ... RelaseCom(xlSheets, "sheets");
KSwordOfHaste

2016/11/29 06:18

C#からCOM利用についての日本語のサイトをいくつか見てみたのですが本件について言及しているものが少なく言及していても非常にあいまいなことしかかいてないように思えました(書いているご本人も明確にわかっていない印象のものしか見つかりませんでした)。自分はC++/Cで使った経験しかないせいかそちらのほうがリファレンスカウンターをどのように扱えばいいのか明確で却ってわかりやすかったです。C#のMSDNの説明は自分も充分把握できてないのですがいっそのことアンマネージドコードも含め中身の実装を見たいと思ったぐらいです。C#について実験レベルでのコメントしかできず中身の仕組みを把握したうえでの回答でないので非常に心苦しく感じます。
a.fuku

2016/11/30 04:00

ご返信ありがとうございます。 初めて本サイトを利用したのですが、そこまで親身になって調べて下さる方がいらっしゃることに感動しました。まだ解決はしておりませんが、大変感謝しております。 解決ができない場合の操作方法の変更も含めて、もう一度プロジェクトのメンバーと相談したいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問