回答編集履歴

2

追記\(指摘・推測\)

2016/11/28 16:16

投稿

KSwordOfHaste
KSwordOfHaste

スコア18394

test CHANGED
@@ -23,3 +23,101 @@
23
23
  その方法とはExcelに対する全ての操作をC#とまったく同じことをするようなVBAで記述し、その動作を確認することです。その際Excelの全てのCOMオブジェクトはテストコードのサブルーチンのローカル変数にし、グローバル変数には一切COMオブジェクトを格納しないことです。このようにして作成したテストサブルーチンを実行してサブルーチンから呼び出し元へ戻ると全てのCOMオブジェクトは(VBAの自動リリース機能によって)完全にリリースされます。その場合に現象がおきないのであればC#プログラムでのリリース漏れの可能性が高いと思います。逆にVBAのテストサブルーチンを実行しても同様の現象になるのであればリリース漏れの可能性は低いと思います。
24
24
 
25
25
 
26
+
27
+ ---
28
+
29
+
30
+
31
+ 追記:2016/11/29 00:01
32
+
33
+ コードを絞り込まれたようなのでそれをベースに自分もC#でCOM操作のコードを書いてみました。以下説明文に(RC=?)と書いている部分がありますが参照カウンターのことと思ってください。
34
+
35
+
36
+
37
+ 1. リファレンスカウンターの確認方法
38
+
39
+ 完全ではないかも知れませんが、Marshal.ReleaseComObjectの戻り値がリリース後のCOMオブジェクトの参照カウンターを表しています。これを利用し、リリース後の参照カウンターが0になるかどうかを確認するというデバッグが可能のようです。このリファレンスカウンターがサーバー側にある本当のカウンターかどうかははっきりわかりませんが、いずれにせよそれが0になるかを確認することは有効なデバッグになると思います。
40
+
41
+ 下の方に「リリースし、かつ結果の参照カウンターをデバッグプリントする」ユーティリティーメソッドReleaseComを書いておきます。
42
+
43
+
44
+
45
+ 2. **finally節のバグ**
46
+
47
+ 質問コードにあるfinally節の処理に問題があると思います。Marshal.ReleaseComObjectにnullを渡すと例外が発生するからです。try本体で何かの例外が発生したり、処理のルートによって一部のCOMオブジェクト格納用の変数がnullのままになっているとfinally節の途中で例外が発生し、それ以降のリリースが行われないので**リリース漏れ**の危険があります。
48
+
49
+ これを回避するには下の方に書いたReleaseComメソッドのようにnullを渡したときに余計なことをしないユーティリティーメソッドを用いると簡単です。
50
+
51
+
52
+
53
+ 3. バグがどうかはっきりわからなかったもの
54
+
55
+ Book.Sheets[index]というコードはリリース漏れの原因になっているような気がしましたがはっきりしません。このコードを実行するとExcel側では次のことが起こるはずです。
56
+
57
+ (1)WorkbookのSheetsプロパティーを返す。結果はWorksheetsオブジェクト(RC=1)
58
+
59
+ (2)Worksheetsのitemメソッドを実行。結果はWorksheetオブジェクト(RC=1)
60
+
61
+ 質問のコードでは(1)をリリースしていないように見えます。でも実際に実行してみると(1)をリリースしてもしなくても違いが見えませんでした。ここはどういう仕組みになっているのか自分にもわかりませんが、自分なら安全側に考えて、
62
+
63
+ sheets = Book.Sheets;
64
+
65
+ sheet = sheets[index];
66
+
67
+ ...
68
+
69
+ ReleaseCom(sheet, "Sheet");
70
+
71
+ ReleaseCom(sheets, "Sheets");
72
+
73
+ と書きます。(自信なし)
74
+
75
+
76
+
77
+ 4. リリースの場所
78
+
79
+ 質問のコードではメソッドの引数に渡されるxlApp, xlBookをメソッドの中でリリースしています。調べてみるとこれは奇妙な実装に見えてきました。COMオブジェクトを引数に渡しても参照カウンターは上がりません。ゆえにこのメソッドでリリースし、呼び出し元でもリリースすると「リリース過多」となりRC=-1になってしまいます。MSDNをざっと読むとRC=-1のオブジェクトに対して何かのメソッド呼び出しやプロパティー参照をするとunmanagedコードの中で危険なことが起こるように見えました。
80
+
81
+ よってリリース対象は「そのメソッドの中でプロパティー参照やメソッド参照を通じて新たに取得したCOMオブジェクトだけ」にするのが適切だと思います。
82
+
83
+
84
+
85
+ ```C#
86
+
87
+ Excel.WorkBook xlBook = ...;
88
+
89
+ ...
90
+
91
+ ReleaseCom(xlBook, "book"); //<=このように使う
92
+
93
+
94
+
95
+ ...
96
+
97
+ public static void ReleaseCom(object o, string msg) {
98
+
99
+ if (o != null) {
100
+
101
+ int rc = Marshal.ReleaseComObject(o);
102
+
103
+ log("released rc={0} {1}", rc, msg);
104
+
105
+ } else {
106
+
107
+ log("not released {0}", msg);
108
+
109
+ }
110
+
111
+ }
112
+
113
+
114
+
115
+ public static void log(string format, params object[] args) {
116
+
117
+ Debug.WriteLine(string.Format(format, args));
118
+
119
+ }
120
+
121
+ ```
122
+
123
+

1

追記(リリース漏れ検証方法)

2016/11/28 16:16

投稿

KSwordOfHaste
KSwordOfHaste

スコア18394

test CHANGED
@@ -7,3 +7,19 @@
7
7
 
8
8
 
9
9
  上の回答には「リリース漏れ問題を撲滅する必要がある」と書きましたが、逆を言えば「**リリース問題さえ無くせばオブジェクトは解放できるはず**」と思います。
10
+
11
+
12
+
13
+ ---
14
+
15
+
16
+
17
+ 追記:2016/11/28 21:17
18
+
19
+ 気づかずにリリース漏れしていることを客観的に確認しやすい方法が一つ考えられます。面倒なのですが調査が難航しているなら(かつ充分問題がしぼりこめているなら)やってみる価値があるかも知れません。
20
+
21
+
22
+
23
+ その方法とはExcelに対する全ての操作をC#とまったく同じことをするようなVBAで記述し、その動作を確認することです。その際Excelの全てのCOMオブジェクトはテストコードのサブルーチンのローカル変数にし、グローバル変数には一切COMオブジェクトを格納しないことです。このようにして作成したテストサブルーチンを実行してサブルーチンから呼び出し元へ戻ると全てのCOMオブジェクトは(VBAの自動リリース機能によって)完全にリリースされます。その場合に現象がおきないのであればC#プログラムでのリリース漏れの可能性が高いと思います。逆にVBAのテストサブルーチンを実行しても同様の現象になるのであればリリース漏れの可能性は低いと思います。
24
+
25
+