【NetOffice】リボンの実装からCOMテクノロジをのぞき見る。

  • 2012.11.26 Monday
  • 20:37
JUGEMテーマ:コンピュータ



Source and Project

-----------------------------------------------------
Summary
VSTOやNetOfficeなどを利用しなくても、ExcelUIリボンをアプリケーションレベルでカスタマイズ可能である。

ExcelUIリボンのコードからカスタマイズしたい場合は、GUIDが000C0396-0000-0000-C000-000000000046で指定されたインターフェイスを利用すると良い。
■VSTOを利用する場合、office.dllの以下のインターフェイスを利用する。
 Microsoft.Office.Core.IRibbonExtensibility
  VSTOの場合は、自動生成してくれる部分があるので簡略化できる。
■NetOfficeを利用する場合、OfficeApi.dllの以下のインターフェイスを利用する。
 NetOffice.OfficeApi.IRibbonExtensibility
■自作する場合は、GUIDを000C0396-0000-0000-C000-000000000046にする。
-----------------------------------------------------

順番からすると、先にNetOfficeを使ってアプリケーションレベルでのリボンインターフェイスの実装を紹介するのが先かと思うのですが、当Blogは、興味のあることを書いていくというスタンスで、好き勝手にやらさせてもらっているので、順番無視して、私がよくわかっていないCOMっていうテクノロジーをのぞき見たよっていう投稿をしたいと思います。

「あれ?」って思ったんです。最初に見たときに。NetOfficeを利用して、リボンインターフェイスを実装しようとすると

1.Extensibility.dllを参照させて、IDTExtensibility2インターフェイスを継承したクラスを作成する。
2.クラスやdllがCOM参照できるように属性を設定。
3.レジストリー登録・解除用メソッドを作成。

1,2,3の手順を行えば、Excel用のCOMアドインは作成できるのですが、ここにNetOfficeのライブラリーを参照させる必要はなく、実装できます。でも、リボンUIを実装しようとすると、話がかわります。まず、OfficeApi.dllを参照させて、IRibbonExtensibilityインターフェイスを継承させ、GetCustomUIメソッドを実装しなければありません。これが「あれ?」と思うわけです。ものすごく違和感を感じるのです。Excelよりも後発で作られたはずのOfficeApi.dllに定義されたIRibbonExtensibilityインターフェイスを、Excelは、どうやって知って呼び出したのだろうと。.NETの世界で、これを完結させようとすると、あらかじめExcel側がインターフェイス名とメソッド名を知っているというか、識別子だけ定義しておき、それに併せてOfficeApi.dllの開発者がインターフェイスを定義すれば、リフレクション経由で何とか呼び出せそうです。ただし、名前空間まで同じにしておく必要があります。そんな事してるのかな?なんて、思いまして、.NetOfficeのソースコード眺めていたらあることに気がつきました。IRibbonExtensibilityインターフェイスの属性に指定されているGUIDとVSTO(http://msdn.microsoft.com/en-us/library/microsoft.office.core.iribbonextensibility.aspx)で定義されているIRibbonExtensibilityインターフェイスのGUIDが一致いている。

 [ComImport, ComVisible(true), Guid("000C0396-0000-0000-C000-000000000046"), TypeLibType((short) 4160)]
 public interface IRibbonExtensibility
 {
     ...
 }

このGUIDが重要なんです。このGUIDをレジストリで検索を書けてみると



HKEY_LOCAL_MACHINE¥SOFTWARE¥Classes¥Wow6432Node¥Interface¥{000C0396-0000-0000-C000-000000000046}という場所にIRibbonExtensibilityという値が定義されていまして、もしかしてGUIDを併せてインターフェイスを定義すれば、別にNetOffiiceのライブラリーを参照しなくても、勝手にExcelが読み込んでくれるのではないかと、思い立ち実験してみることにしてみました。

で、書いたコードがこれ、VSTOもNetOfficeでもなく、IRibbonExtensibilityを自作する。後で調べて分かった事ですが、インターフェイスの名前は何でも良いようです。とにかくGUIDを000C0396-0000-0000-C000-000000000046にすればいいようです。また、COMにインポートできるようにComImport属性をつけ、COM参照できるようにComVisible(true)にする必要があります。

using System.Runtime.InteropServices;

namespace Art55.NetOfficeDemo20121126_001
{
    [ComImport, ComVisible(true), Guid("000C0396-0000-0000-C000-000000000046")]
    public interface IRibbonExtensibility
    {
        string GetCustomUi(string ribbonId);
    }
}

これを利用して、アドインクラスを作成すると

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Extensibility;
using Microsoft.Win32;

namespace Art55.NetOfficeDemo20121126_001
{
    [GuidAttribute("15071DAF-177C-4438-9129-4B4DB82BFF81"), ProgId(AddInProcId), ComVisible(true)]
    public class ExcelComAddin : IDTExtensibility2, IRibbonExtensibility
    {
        private const string AddInProcId = "Art55.Excel.ExcelComAddin";
        private const string SubKeyPath = @"Software¥Microsoft¥Office¥Excel¥Addins¥" + AddInProcId;

        [ComRegisterFunctionAttribute]
        public static void RegisterFunction(Type type)
        {
            try
            {
                // add codebase value
                Assembly thisAssembly = Assembly.GetAssembly(typeof(ExcelComAddin));
                RegistryKey key = Registry.ClassesRoot.CreateSubKey("CLSID¥¥{" + type.GUID.ToString().ToUpper() + "}¥¥InprocServer32¥¥1.0.0.0");
                key.SetValue("CodeBase", thisAssembly.CodeBase);
                key.Close();

                key = Registry.ClassesRoot.CreateSubKey("CLSID¥¥{" + type.GUID.ToString().ToUpper() + "}¥¥InprocServer32");
                key.SetValue("CodeBase", thisAssembly.CodeBase);
                key.Close();

                // add bypass key
                // http://support.microsoft.com/kb/948461
                key = Registry.ClassesRoot.CreateSubKey("Interface¥¥{000C0601-0000-0000-C000-000000000046}");
                var defaultValue = key.GetValue("") as string;
                if (null == defaultValue)
                    key.SetValue("", "Office .NET Framework Lockback Bypass Key");
                key.Close();

                Registry.CurrentUser.CreateSubKey(SubKeyPath);
                RegistryKey regKeyExcel = Registry.CurrentUser.OpenSubKey(SubKeyPath, true);
                regKeyExcel.SetValue("LoadBehavior", 3);
                regKeyExcel.SetValue("FriendlyName", "ExcelComAddin");
                regKeyExcel.SetValue("Description", "DESC");
                regKeyExcel.Close();
            }
            catch (Exception)
            {
                Debugger.Launch();
            }
        }


        [ComUnregisterFunction]
        public static void UnregisterFunction(Type type)
        {
            try
            {
                Registry.ClassesRoot.DeleteSubKey(@"CLSID¥{" + type.GUID.ToString().ToUpper() + @"}¥Programmable", false);
                Registry.CurrentUser.DeleteSubKey(SubKeyPath, false);
            }
            catch (Exception)
            {
                Debugger.Launch();
            }
        }

        void IDTExtensibility2.OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
        {
        }

        void IDTExtensibility2.OnDisconnection(ext_DisconnectMode removeMode, ref Array custom)
        {
        }

        void IDTExtensibility2.OnAddInsUpdate(ref Array custom)
        {
        }

        void IDTExtensibility2.OnStartupComplete(ref Array custom)
        {
        }

        void IDTExtensibility2.OnBeginShutdown(ref Array custom)
        {
        }

        public string GetCustomUi(string ribbonId)
        {
            return ReadRessourceFile("RibbonUI.xml");
        }

        protected internal static string ReadRessourceFile(string fileName)
        {
            Assembly assembly = typeof(ExcelComAddin).Assembly;
            using (Stream ressourceStream = assembly.GetManifestResourceStream(assembly.GetName().Name + "." + fileName))
            {
                if (ressourceStream == null)
                    throw (new IOException("Error accessing resource file"));

                using (var textStreamReader = new StreamReader(ressourceStream))
                {
                    return textStreamReader.ReadToEnd();
                }
            }
        }

        public void OnAction(object sender)
        {
            MessageBox.Show(sender.ToString());
        }

    }
}

赤字にしたところがUIリボンの読み込みの実装です。見ての通り、VSTOもNetOfiiceも利用していません。これをCOM登録して、Excelを実行してみると



OnActionのコールバックも、シグニチャをあせてpublicで定義しておけば、キチンをコールバックされると言うこともわかりました。

という事で、これをいわゆるCOMってヤツなんだと思うのですが、勉強不足でこれ以上はまだ分かっていません。兎にも角にもExcel側で提供されているGUIDに合わせて型やメソッドを定義してあげれば、実行時にExcelがCOMアドインを読み込む際に、自動で読み込んでくれるということがわかりました。NetOfficeのIRibbonExtensibilityをなぜExcelは知ってるのだろうという、疑問はとりあえず解消です。

今回の仕組みはIDTExtensibility2やICustomTaskPaneConsumerも同じ事が言えそうです。機会があれば調べてみたいと思います。

コードで話を展開されてしまったので、ちょっと絵を交えながら、補足説明をしていきたいと思います。

私の最初の誤解は以下のような感じでした。



Excelとアドインの両方がしっている共通のライブラリーが存在し、そこにIRibbonExtensibilityインターフェイスが定義されている。しかし、これには矛盾があります。それは共通インターフェイスと思われるライブラリーがNetOfficeの中で完結しており、Excelがリリースされたずいぶん後に作成されたライブラリーなのでExcelがそのインターフェイスの存在なんて知るわけもない。

それじゃー。したみたいな感じならどうだろうって思ったんです。



名前空間の扱いってどうなんだろうとか、そもそもExcelは.NET Framework上で動作しているわけでもないので、なんかおかしいって事で、考えは一瞬で否定しました(笑)

で、GUIDに着目した訳です。


Excelインストール時に、登録されたGUIDと型との関連づけがレジストリに登録されており、このGUIDが、アドイン読み込み時に、アドインが知っているインターフェイスの実装を実行するようにExcelが呼び出す事ができる!・・・たぶん。

と、思うのですが、COMテクノロジーに関して不勉強という無勉強なので単なる想像です。もう少し時間が必要です。

--------------------
感想。
よくわからないCOMってやつをしる足がかり的なものを手に入れてちょっと小躍りしたくなるくらい、嬉しいです。こういうのが楽しいので、プログラミングはやめられません。

Source and Project

--------------------------------------
本投稿は、CodePlexで公開されているNetOfficeを利用していませんが、紹介します。
NetOffice - MS Office in .NET
http://netoffice.codeplex.com/

NetOffice関連の記事は下記にまとめています。
【NetOffice】【Excel】NetOfficeのまとめ
http://pro.art55.jp/?eid=1304102
--------------------------------------

コメント
コメントする








    
この記事のトラックバックURL
トラックバック

calendar

S M T W T F S
     12
3456789
10111213141516
17181920212223
24252627282930
31      
<< December 2017 >>

あわせて読みたい

あわせて読みたいブログパーツ

selected entries

categories

archives

recent comment

  • 【キーボード】6年前のRealForceを復活させることはできる!?その3
    art55 (05/22)
  • 【キーボード】6年前のRealForceを復活させることはできる!?その3
    分解大好き (05/18)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    art55 (02/04)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    Gen (02/04)
  • 【キーボード】RealForce が壊れて帰ってきた。
    art55 (04/29)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    art55 (02/23)
  • 【.NET Framework 4.5】 IListがIReadOnlyListを継承してない理由。
    かるあ (02/22)
  • 【C#】Dictionaryの実装・データ構造・アルゴリズムを観察する。
    art55 (01/16)
  • 【C#】Dictionaryの実装・データ構造・アルゴリズムを観察する。
    karuakun (01/16)
  • 【NetOffice】【Excel】死なないExcelプロセスをKillする。
    art55 (12/05)

recent trackback

recommend

recommend

recommend

C#プログラマのための.NETアプリケーション最適化技法 (Programmer's SELECTION)
C#プログラマのための.NETアプリケーション最適化技法 (Programmer's SELECTION) (JUGEMレビュー »)
Sasha Goldshtein,Dima Zurbalev,Ido Flatow,サシャ・ゴルドシュタイン,ディマ・ズルバレフ,イド・フラトー

recommend

ろんりと集合
ろんりと集合 (JUGEMレビュー »)
中内 伸光
とてもわかりやすいです。

recommend

recommend

シャノン・ノイマン・ディジタル世界
シャノン・ノイマン・ディジタル世界 (JUGEMレビュー »)
市川 忠男
4章がリレーショナルデータベースな内容になってます。ページ数があまりありませんが、ポイントがものすごく的確にまとまっていて、感動します。

recommend

recommend

東プレ Realforce91UBK-S 静音キーボード 静電容量無接点方式 変荷重 ブラック NG01BS
東プレ Realforce91UBK-S 静音キーボード 静電容量無接点方式 変荷重 ブラック NG01BS (JUGEMレビュー »)

テンキーレス、静音のRealForce91UBK-S。スコスコ感がたまらなく気持ちいいです。家と会社で2台持ってます。

recommend

recommend

プログラミング.NET Framework 第4版 (プログラミングシリーズ)
プログラミング.NET Framework 第4版 (プログラミングシリーズ) (JUGEMレビュー »)
Jeffrey Richter
発売予定美 2013年10月10日。.NET Frameworkとお付き合いする人のバイブルですね。

recommend

recommend

キャット・シッターの君に。
キャット・シッターの君に。 (JUGEMレビュー »)
喜多嶋 隆
私のイラストレータデビュー本です。

recommend

Essential .NET ― 共通言語ランタイムの本質
Essential .NET ― 共通言語ランタイムの本質 (JUGEMレビュー »)
ドン・ボックス,クリス・セルズ,Don Box,Chris Sells,吉松 史彰

links

profile

search this site.

others

mobile

qrcode

powered

無料ブログ作成サービス JUGEM