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

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

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

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

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

VB.NET

Microsoft Visual Basic .NETのことで、Microsoft Visual Basic(VB6)の後継。 .NET環境向けのプログラムを開発することができます。 現在のVB.NETでは、.NET Frameworkを利用して開発を行うことが可能です。

Q&A

解決済

3回答

4947閲覧

C# 遅延バインディングで実行しているExcel COMオブジェクトのイベントをフックする方法

naitou

総合スコア141

C#

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

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

VB.NET

Microsoft Visual Basic .NETのことで、Microsoft Visual Basic(VB6)の後継。 .NET環境向けのプログラムを開発することができます。 現在のVB.NETでは、.NET Frameworkを利用して開発を行うことが可能です。

0グッド

1クリップ

投稿2022/01/05 03:36

編集2022/01/06 06:05

前提として、Excelバージョンに依存しない様にラッパークラスであるMicrosoft.Office.Interop.Excelは利用せずに、遅延バインディングしたいと思っております。

64ビットビルドすると、
Type.GetTypeFromProgID("Excel.Application")
で取得したTypeからGetEventsメソッドやGetMethodsメソッドを呼んでも
Microsoft.Office.Interop.Excel.Applicationクラスの
のイベントやメソッドが取得できません。

ここでイベントが取れない為、イベントハンドラーが登録できず困りました。

まさに下記ページの方法を行おうとしておりますが、64ビットビルドだとGetEventメソッドの戻り値がnullになってしまいイベントが取れません。32ビットビルドでは正常に動作します。
https://littlepower-suesan.hatenadiary.org/entry/20110628/1309266642

このページの方法は試しておりませんが、GUID指定によりExcelバージョンに依存してしまう為除外しています。
https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/dc59b406-3f86-4fac-817a-f4fafd3ffdb9/36933243101249612452125311248712451125311246412391excel1239812452?forum=csharpgeneralja

見識のある方がいらっしゃいましたら、よろしくお願いします。


2020年1月5日18時00分追記

環境検証、ソースコード、実行結果を記載します。

検証環境
OS:Windows10 20H2
ビルド環境:Microsoft Visual Studio Professional 2019 Version 16.11.3
フレームワーク:.NET Framework4.8
Excel2013 15.0.5381.1000 32ビット

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Reflection; 5using System.Threading.Tasks; 6using System.Windows.Forms; 7using System.Threading; 8namespace WindowsFormsApp13 { 9 10 static class Program { 11 12 [STAThread] 13 static void Main() { 14 Console.WriteLine("ビルド:"+(IntPtr.Size==4?"32ビット":"64ビット")); 15 16 Type objeType = Type.GetTypeFromProgID("Excel.Application"); 17 18 dynamic _APPLICATION = Activator.CreateInstance(objeType); 19 20 _APPLICATION.Visible = true; 21 22 EventInfo eventInfo = _APPLICATION.GetType().GetEvent("SheetSelectionChange"); 23 24 if(eventInfo!=null){ 25 Console.WriteLine("OK"); 26 27 Type eventType = eventInfo.EventHandlerType; 28 29 Type type = typeof(Program); 30 31 MethodInfo methodInfo = type.GetMethod("SheetSelectionChangeEvent"); 32 33 Delegate d = Delegate.CreateDelegate(eventType, methodInfo); 34 35 eventInfo.AddEventHandler(_APPLICATION, d); 36 }else{ 37 Console.WriteLine("NG"); 38 } 39 Console.ReadKey(); 40 } 41 42 public static void SheetSelectionChangeEvent(object sheet, object range){ 43 Console.WriteLine("SheetSelectionChangeEvent"); 44 } 45 } 46}

実行結果はビルド毎に下記の通りとなりました。

・32ビットビルド時
※起動したエクセルで新規ファイルを開いてセル選択を何度か実行

ビルド:32ビット
OK
SheetSelectionChangeEvent
SheetSelectionChangeEvent
SheetSelectionChangeEvent
SheetSelectionChangeEvent

・64ビットビルド時

ビルド:64ビット
NG


2020年1月6日15時00分追記

現象が出るマシンと出ていないマシンがあり、
環境に依存して現象が発生しているようです。
現象が出るマシンと出ていないマシンの違いは次の通りでした。

現象が出ているマシンで64ビットビルドしたものは、
Type.GetTypeFromProgID("Excel.Application")で
Typeとして、「System.__ComObject」が返って来ていました。

しかし32ビットビルドだと
Typeとして、「Microsoft.Office.Interop.Excel.ApplicationClass」が
返って来ます。

現象が出ていないマシンでは、
32ビットと64ビットのどちらでも、
Typeとして、「Microsoft.Office.Interop.Excel.ApplicationClass」が返って来ます。

COMオブジェクトの場合はGetType()をすると「System.__ComObject」という
クラスで取得されることもあるようです。
どのようなケースでそうなるか明確な情報は見つけられませんでした。

下記URLでも類似の現象が出ており、
Excel(ストアアプリ)→ オフィス2016のデスクトップ版
の変更により解消した様です。
しかしストアアプリでも遅延バインディングは利用できる為、今回現象とは関係ないかもしれません。
https://social.technet.microsoft.com/Forums/Windows/ja-JP/77942146-7be9-4409-81cc-aa19e1ba4ad9/1245012503125221236312425excel12364216281240320986123791239412356?forum=Office2016ITProJP

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

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

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

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

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

Zuishin

2022/01/05 03:45

参考にしたソースコードをコピペしてそのまま使い、記載されていることができるかどうかをまず確認してください。 それでできるなら、あなたが応用して書いた部分が間違っているので、それを記述してください。
退会済みユーザー

退会済みユーザー

2022/01/05 04:48 編集

そりゃCOM側とアーキテクチャ合わせないと動かないのでは。
退会済みユーザー

退会済みユーザー

2022/01/05 05:12 編集

後から判明した原因等は、元の質問文は変えずに追記してください。 最初の質問文と解決方法が変わってくるので、既にした回答と食い違いが出てきます。
naitou

2022/01/05 05:31

すみません。質問分は今後気を付けます。
退会済みユーザー

退会済みユーザー

2022/01/05 06:31 編集

上のアーキテクチャ合わせるというのは勘違いだったようです、失礼しました。 こちらでリンク先の記事と同じように試した所、64bit・32bit共に問題なくイベントは動作しました。(Excel2013 32bit) そちらで動作確認に使用したソースコードを載せてみてください。
naitou

2022/01/05 09:12

ソースコードを記載しました。こちらもExcel2013ですが、64bitの場合はダメです。アーキテクチャを合わせるといのは、64ビット版のExcelでないとダメなのかと思いました。
guest

回答3

0

できないものはしょうがないので、イベントをキャッチする方法を。
ただし、DispID を調べる必要があります。

(1) イベントを補足するための SinkObject を書きます。

C#

1using System; 2using System.Collections.Generic; 3using System.Runtime.InteropServices; 4using ComTypes = System.Runtime.InteropServices.ComTypes; 5 6[ComVisible(true)] 7public abstract class SinkObject : StandardOleMarshalObject, IDisposable 8{ 9 readonly List<CPEntry> entries; 10 11 protected SinkObject(object comObject) { 12 var cpc = comObject as ComTypes.IConnectionPointContainer; 13 if (cpc == null) { 14 return; 15 } 16 entries = new List<CPEntry>(); 17 cpc.EnumConnectionPoints(out ComTypes.IEnumConnectionPoints enmcp); 18 if (enmcp != null) { 19 var cp = new ComTypes.IConnectionPoint[10]; 20 while (true) { 21 IntPtr countPtr = Marshal.AllocCoTaskMem(sizeof(int)); 22 try { 23 Marshal.WriteInt32(countPtr, 0); 24 if (FAILED(enmcp.Next(cp.Length, cp, countPtr))) { 25 break; 26 } 27 int count = Marshal.ReadInt32(countPtr); 28 if (count == 0) { 29 break; 30 } 31 for (int i = 0; i < count; i++) { 32 entries.Add(new CPEntry(this, cp[i])); 33 } 34 } finally { 35 Marshal.FreeCoTaskMem(countPtr); 36 } 37 } 38 Marshal.ReleaseComObject(enmcp); 39 } 40 } 41 42 private static bool FAILED(int hr) { 43 return hr < 0; 44 } 45 46 public bool IsDisposed { get; private set; } 47 48 protected virtual void Dispose(bool disposing) { 49 if (!IsDisposed) { 50 IsDisposed = true; 51 if (disposing) { 52 foreach (var cp in entries) { 53 cp.Dispose(); 54 } 55 } 56 } 57 } 58 59 public void Dispose() { 60 Dispose(true); 61 GC.SuppressFinalize(this); 62 } 63 64 ~SinkObject() { 65 Dispose(false); 66 } 67 68 private class CPEntry : IDisposable 69 { 70 private readonly ComTypes.IConnectionPoint connectionPoint; 71 private readonly int cookie; 72 73 public CPEntry(SinkObject owner, ComTypes.IConnectionPoint cp) { 74 connectionPoint = cp; 75 cp.Advise(owner, out cookie); 76 } 77 78 public bool IsDisposed { get; private set; } 79 80 protected virtual void Dispose(bool disposing) { 81 if (!IsDisposed) { 82 IsDisposed = true; 83 if (disposing) { 84 } 85 this.connectionPoint.Unadvise(cookie); 86 Marshal.ReleaseComObject(connectionPoint); 87 } 88 } 89 90 public void Dispose() { 91 Dispose(true); 92 GC.SuppressFinalize(this); 93 } 94 95 ~CPEntry() { 96 Dispose(false); 97 } 98 } 99}

(2) (1) を継承し、オブジェクト固有のイベントを補足するためのクラスを作ります。
ここで DispId の調査が必要になります。
Application クラスの NewWorkBokk イベント(DispId(1565)) を補足してみます。

C#

1using System; 2using System.ComponentModel; 3using System.Runtime.InteropServices; 4 5[ComVisible(true)] 6public class AppEvents : SinkObject 7{ 8 public AppEvents(object comObject) : base(comObject) { } 9 10 [DispId(1565)] 11 public void OnNewWorkbook([In, MarshalAs(UnmanagedType.IDispatch)] ref object wb) { 12 NewWorkbook?.Invoke(wb); 13 } 14 15 public delegate void NewWorkbookDelegate(object wb); 16 public event NewWorkbookDelegate NewWorkbook; 17}

(3) 使い方

C#

1public partial class Form1 : Form 2{ 3 dynamic app; 4 AppEvents events; 5 6 public Form1() { 7 InitializeComponent(); 8 } 9 10 private void Form1_Load(object sender, EventArgs e) { 11 Type objeType = Type.GetTypeFromProgID("Excel.Application"); 12 app = Activator.CreateInstance(objeType); 13 events = new AppEvents(app); 14 events.NewWorkbook += OnNewWorkbook; 15 app.Visible = true; 16 } 17 18 private void button1_Click(object sender, EventArgs e) { 19 dynamic wb = app.Workbooks; 20 object newBook = wb.Add(); 21 Marshal.ReleaseComObject(wb); 22 Marshal.ReleaseComObject(newBook); 23 } 24 25 private void OnNewWorkbook(object wb) { 26 MessageBox.Show("App_NewWorkbook"); 27 Marshal.ReleaseComObject(wb); 28 } 29 30 private void Form1_FormClosed(object sender, FormClosedEventArgs e) { 31 app.DisplayAlerts = false; 32 app.Quit(); 33 events.Dispose(); 34 Marshal.ReleaseComObject(app); 35 } 36}

投稿2022/01/06 07:52

KOZ6.0

総合スコア2707

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

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

naitou

2022/01/06 07:56

時間差ですが、回答ありがとうございます。色々使えそうなので今後の参考にさせて頂きます。
guest

0

自己解決

64ビットビルドした際に、GetTypeFromProgIDではなぜかTypeが上手くとれていないマシンがあり原因は全くわかりません。
もし原因がわかる人がいたらいたら教えて頂きたいです。

力技ですが、そのケースにおいて、C:\WINDOWS\assembly\GAC_MSIL\Microsoft.Office.Interop.Excel 以下を
検索して、Microsoft.Office.Interop.Excel.dllを直接読み込む様にしました。

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Reflection; 5using System.Threading.Tasks; 6using System.Windows.Forms; 7using System.Threading; 8using System.Runtime.InteropServices; 9using System.Runtime.InteropServices.ComTypes; 10using System.IO; 11namespace WindowsFormsApp13 { 12 13 static class Program { 14 15 [STAThread] 16 static void Main() { 17 18 Console.WriteLine("ビルド:"+(IntPtr.Size==4?"32ビット":"64ビット")); 19 20 Type objeType = Type.GetTypeFromProgID("Excel.Application"); 21 22 Console.WriteLine("Type名:"+objeType.Name+" Typeアセンブリ"+objeType.Assembly.Location); 23 24 EventInfo eventInfo = objeType.GetEvent("SheetSelectionChange"); 25 26 if(eventInfo==null){ 27 var filePaths = Directory.GetFiles(@"C:\WINDOWS\assembly\GAC_MSIL\Microsoft.Office.Interop.Excel","*",SearchOption.AllDirectories); 28 var path = filePaths.Where(x=>Path.GetFileName(x).Contains("Microsoft.Office.Interop.Excel")).FirstOrDefault(); 29 if(path!=null){ 30 Assembly asm = Assembly.LoadFrom(path); 31 objeType = asm.GetType("Microsoft.Office.Interop.Excel.ApplicationClass"); 32 eventInfo = objeType.GetEvent("SheetSelectionChange"); 33 if(eventInfo!=null){ 34 Console.WriteLine("OK Load:"+path); 35 } 36 } 37 }else{ 38 Console.WriteLine("OK Call:Type.GetTypeFromProgID(\"Excel.Application\")"); 39 } 40 41 if(eventInfo!=null){ 42 dynamic _APPLICATION = Activator.CreateInstance(objeType); 43 44 _APPLICATION.Visible = true; 45 46 Type eventType = eventInfo.EventHandlerType; 47 48 Type type = typeof(Program); 49 50 MethodInfo methodInfo = type.GetMethod("SheetSelectionChangeEvent"); 51 52 Delegate d = Delegate.CreateDelegate(eventType, methodInfo); 53 54 eventInfo.AddEventHandler(_APPLICATION, d); 55 }else{ 56 Console.WriteLine("NG"); 57 } 58 Console.ReadKey(); 59 } 60 61 public static void SheetSelectionChangeEvent(object sheet, object range){ 62 Console.WriteLine("SheetSelectionChangeEvent"); 63 } 64 } 65} 66

投稿2022/01/06 07:45

naitou

総合スコア141

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

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

退会済みユーザー

退会済みユーザー

2022/01/06 08:03

開発環境以外だと、デフォルトではGACにMicrosoft.Office.Interop.Excel.dllは入ってなさそうな気がするんですが
naitou

2022/01/06 08:40

Excelをインストールしたら、GACにMicrosoft.Office.Interop.Excel.dll入っているものと思っていました。 開発環境(Visual Studio)がないPCで確認しましたが入っていました。どのような理由で入っていないと思われるのでしょうか。
退会済みユーザー

退会済みユーザー

2022/01/06 08:48

VisualStudioでCOMを参照した時にtlbimpで生成されるんじゃないかと思ってたんですけど。私の杞憂ならすみません。
naitou

2022/01/06 09:00

「VisualStudioでCOMを参照した時にtlbimpで生成」とは、参照の追加で「Microsoft Excel X.X Object Library」(X.Xはバージョン番号)を追加した時ということでしょうか。周りにあるWindows10ではGACにありましたが、7や古いマシンでも大丈夫か注意しておきます。
guest

0

NetOffice Framework
こんなのがありますが。
イベントも使えるみたいです。

投稿2022/01/05 04:16

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

naitou

2022/01/05 04:54

情報ありがとうございます。今後の参考にします。
退会済みユーザー

退会済みユーザー

2022/01/06 00:25 編集

追記されたソースをそのまま実行しましたが、64bitEXEでもイベントが実行され、問題が再現出来ませんでした。もしかしたら環境的な問題なのかもしれません。Microsoft.Office.Interop.Excelを使用した場合、インスタンス作成、イベント実行は正常に行われるのでしょうか? 関係あるかどうかは判りませんが、別バージョンのOfficeのタイプライブラリ情報がレジストリに残っていると、それが不具合を引き起こすような事があるようです。 [64ビット環境でエラー(C#で既存Excelファイルをオープンしたときにエラー)] https://social.msdn.microsoft.com/Forums/security/ja-JP/76e67ade-2be3-47d7-9f04-4736781446db/6412499124831248829872226591239112456125211254065288c1239126082?forum=csharpgeneralja
naitou

2022/01/06 03:35

検証ありがとうございます。Microsoft.Office.Interop.Excelを使用した場合は、インスタンス作成、イベント実行は正常に行えています。そもそもMicrosoft.Office.Interop.Excelで作らて正常に動いているプログラムを遅延バインディング版に修正していてこの問題に遭遇しました。こちらの環境でも別のマシン(Excel2019で64ビット)へ64ビットビルドしたもの入れてみると正常動作するのが確認できました。このマシン特有の問題の可能性が高いかもしれないです。上記リンクも含め、その観点で少し調べてみます。
naitou

2022/01/06 05:55

別バージョンOfficeのタイプライブラリ情報はレジストリには残っていませんでした。記憶上も2013の製品版を1度しかインストールしていません。
退会済みユーザー

退会済みユーザー

2022/01/06 06:04

Microsoft.Office.Interop.Excelだと問題ないんですか、ますます謎ですね… 私の知識の範疇だと、これ以上の情報提供は難しそうです。 NetOfficeも、一度ダメ元で試してみてはいかがでしょう。
naitou

2022/01/06 06:09

現象が出るマシンと出ていないマシンの違いがもう少しわかった為追記しました。もう少し回答を募り、解決しなさそうであればクローズにしたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問