========================================================== How to make Addin Dlls for Sari by Kazuhiro Kito ========================================================= 【はじめに】  SARIのアドインプログラムを作成する手順を説明します。  アドインとSARIとの接続にはCOMを使います。  下記のスケルトン作成手順はVC++6.0で示しています。VC++5.0でもそのまま実 行できるだろうと思います。  VC++でなくても、COMに対応したコンパイラーがあれば、アドインは作成できま す。ただその場合、下記の手順を、そのコンパイラーにあわせて部分変更する必 要が生じるでしょう。COMに関する知識が必要になりますが、要点は、既定のuuid をもったISariAddinインターフェースを公開することと、SARIのコネクションポ イントにつながるシンクインターフェースを作成する、という2点につきます。 ISariAddinインターフェースのuuidが既定であるという点がやや特殊ですが、そ れほど複雑なことを行わなければならないわけではありません。  この文書には次の項目があります。 【VC++でスケルトンプログラムを作成する手順】 【コマンドの登録】 【コマンド実行通知の処理】 【イベント通知の処理】 【付録A:VC++のATL系のバクについて】 【付録B:添付サンプルについて】 * * * * 【VC++でスケルトンプログラムを作成する手順】 <1>新規のプロジェクトを作成します。  プロジェクトをATL COM AppWizardを使って新規作成します。  形式としてDLLを選択し、MFCを使うときは「MFCを使う」チェックボックスをチ ェックします。  プロジェクト名を仮にADTestとして説明します。 <2>次の4つの添付ファイルをプロジェクトディレクトリにコピーします。 Sari_i.c Sari_i.h SariAddin.cpp SariAddin.h <3>シンクオブジェクトをつくる。  このオブジェクトがSARIからのイベント通知を受け取ります。  「挿入」→「ATLオブジェクトの新規作成」から、「シンプルオブジェクト」を 選択し、ショートネーム欄に「Sink」と入力します。名前はなんでもいいのです が、CSariAddinクラスがこのオブジェクトへのポインタを必要とします。任意の 名をつけた場合は、SariAddin.hおよびSariAddin.cppの該当する部分を、その名 前に変更する必要があります。  「アトリビュート」タブでアトリビュート欄を開き、「スレッドモデル」は 「アパートメント」、「インターフェイス」は「デュアル」になっていることを 確認します。「アグリゲーション」のデフォルトは「はい」になっていると思い ますが、これを「いいえ」に変更します。  すべてOKなら、「OK」ボタンを押し、オブジェクト作成を実行します。  クラスビュー欄に、いま作ったオブジェクトのクラスCSinkと、そのインターフ ェイスであるISinkが表示されます。 <4>Sink.hファイルを開き、次の変更を加えます。  まず添付ファイルSari_i.hをインクルードします。  すなわち、 #include "resource.h" // メイン シンボル のところを、 #include "resource.h" // メイン シンボル #include "sari_i.h"  とします。  次にSARIからのイベント通知を受け取るため、 BEGIN_COM_MAP(CSink) COM_INTERFACE_ENTRY(ISink) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() とある部分に、_ISRAPLEventsのエントリを加えます。 すなわち、 BEGIN_COM_MAP(CSink) COM_INTERFACE_ENTRY(ISink) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY_IID(DIID__ISRAPLEvents, IDispatch) END_COM_MAP()  とします。  次に、3つのメンバー変数を作り、コンストラクタで初期化します。 CSink() { m_pApl=NULL; m_dwCookie=0; m_dwAdvise=0; } ISRAPL *m_pApl; DWORD m_dwCookie; DWORD m_dwAdvise;  次に、通知を受け取るためのメソッドを8つ追加します。  クラスビューでISinkを右クリックし、「メソッドの追加」コマンドを実行すれ ば追加できます。 メソッド名はそれぞれ OnAddinCommand OnNewInstance OnPreOpen OnPostOpen OnPreSave OnPostSave OnCloseInstance OnBroadcastMessage  です。上記の順番どおりに作成してください。  OnAddinCommanndとOnBroadcastMessageにはパラメータが必要です。  OnAddinCommandのパラメータ欄には次のパラメータを記入します。 [in]long lCookie, [in]long lInnerNumber  OnBroadcastMessageのパラメータ欄には次のパラメータを記入します。 [in]long lCookie, [in]long lMessage  他の6つのメソッドにはパラメータがありませんから、パラメータ欄は空白の ままにしておきます。  この作業がおわると、次のメソッドがクラス定義に加わります。 STDMETHOD(OnBroadcastMessage)(/*[in]*/long lCookie, /*[in]*/long lMessage); STDMETHOD(OnCloseInstance)(); STDMETHOD(OnPostSave)(); STDMETHOD(OnPreSave)(); STDMETHOD(OnPostOpen)(); STDMETHOD(OnPreOpen)(); STDMETHOD(OnNewInstance)(); STDMETHOD(OnAddinCommand)(/*[in]*/long lCookie, /*[in]*/long lInnerNumber);  最後に次に二つの関数を付け加えます。属性はpublicにしてください。  CSinkの定義の末尾に次の三行をそのままコピーしてくだされば結構です。 public: BOOL SetObjectInfo(ISRAPL *pApl, DWORD dwCookie); void Unadvise(); <5>Sink.cppに必要な変更を加えます。  Sink.cppには、イベント通知を受け取るためのメソッドのスケルトンがすでに 付け加わっているはずです。ただ、前段の最後で追加したふたつの関数について は、手動でヘッダに追加しましたから実体がありません。そこで、Sink.cppの冒 頭部分に次の関数をコピーしてください。これらは、SARIとの接続を確立し、そ して接続を切断するための関数です。 BOOL CSink::SetObjectInfo(ISRAPL *pApl, DWORD dwCookie) { HRESULT hr; ASSERT(pApl!=NULL && m_pApl==NULL); m_pApl=pApl; ASSERT(dwCookie>0 && m_dwCookie==0); m_dwCookie=dwCookie; hr = AtlAdvise(m_pApl, this->GetUnknown(), DIID__ISRAPLEvents, &m_dwAdvise); if(FAILED(hr)){ m_pApl->Release(); m_pApl=NULL; m_dwCookie=0; MessageBox(NULL, "Advise Failed","", MB_OK); return FALSE; } return TRUE; } void CSink::Unadvise() { if(m_pApl==NULL) return; HRESULT hr=AtlUnadvise(m_pApl, DIID__ISRAPLEvents, m_dwAdvise); if(FAILED(hr)){ MessageBox(NULL, "Unadvise Failed", "", MB_OK); } m_pApl->Release(); m_pApl=NULL; } <6>添付SariAddin.hとSariAddin.cppを、「プロジェクト」→「プロジェクト への追加」→「ファイル」で、プロジェクトに追加します。  もしシンクオブジェクトの名前をSink以外のものにした場合は、下記の箇所を 該当する名前に変更してください。 (a)#include "Sink.h"の部分。 (b)CComObjectの部分。 (a) HRESULT hr=CComObject< CSink >::CreateInstance(&m_pSink);  CSariAddinのメンバー変数m_pSinkという名前も変更した場合は、その部分もす べて修正する必要があります。 <7>SariAddin.hをあなたのプログラムにあわせて修正します。  まず冒頭部に説明があるように、CSariAddinオブジェクトのためのクラスIDを 作成し、該当部分に記入します。  クラスIDは、VC++に付属するGuidgen.exe等のツールを使い、必ず独自のものを 作成するようにしてください。  次に DECLARE_REGISTRY(CSariAddin, "SomeProj.SariAddin.1", "SomeProj", IDS_SOMEPROJ_LONGNAME, THREADFLAGS_APARTMENT)  という部分があります。これは作成したオブジェクトをSARIが使えるようにレ ジストリに登録するためのものです。  2箇所の修正と、リソースをひとつ作成する必要があります。  修正のひとつは "SomeProj.SariAddin.1"という部分です。  SomeProjという部分をあなたが作成しようとしているプロジェクトの名前にし てください。いまの例なら、"ADTest.SariAddin.1"となります。  次の引数"SomeProj"も同様に"ADTest"にかえます。  次にIDS_SOMEPROJ_LONGNAMEという引数がありますが、これはリソースのIDで す。プログラムの説明を現すストリング・リソースを作成し、そのIDをIDS_ SOMEPROJ_LONGNAMEと置き換えてください。プログラムの説明としては、たとえば  "ADTest, an addin dll for Sari"などでいいでしょう。   <8>SariAddin.cppにADTest.hをインクルードします。 すなわち、 #include "stdafx.h" #include "Sari_i.h" #include "SariAddin.h" とあるのを、 #include "stdafx.h" #include "ADTest.h" #include "Sari_i.h" #include "SariAddin.h" とします。 <9>ADTest.cppファイルを開き、次の修正を加えます。 (a) 次のふたつのファイルをインクルードします。 #include "Sari_i.c" #include "SariAddin.h" (B)オブジェクトマップに次のエントリを加えます。 すなわち、 BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Sink, CSink) END_OBJECT_MAP() とあるのを、下記のようにします。 BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Sink, CSink) OBJECT_ENTRY(CLSID_SariAddin, CSariAddin) END_OBJECT_MAP() (CSinkは外部で作成されませんから、CSinkのエントリはコメントアウ トしてもいいです。そのままでも問題はありません。) <1>から<9>でスケルトンは出来上がりました。  一度、コンパイルしてみましょう。  無事、コンパイルできましたか?  コンパイルできなければ、なにかが間違っています。説明をもう一度、注意 深く読んでください。どうしてもコンパイルできなければ、SARIの作者までお問 い合わせください。問題解決のお手伝いができるかもしれません。  コンパイルが成功したものとして、コマンドをSARIに登録し、SARIからコマン ドの実行その他の通知を受け取るにはどうすればよいかを、説明します。 * * * * 【コマンドの登録】  コマンドは0個以上、任意の数を登録できます。  (ただ、ユーザーが組み込むアドインプログラムのコマンドをすべて足しあわ して、1000個未満という制約はあります。)  登録はCSariAddinのメンバー関数OnConnectionの中で行います。  コマンドの登録は、 HRESULT hr=CComObject< CSink >::CreateInstance(&m_pSink);  でシンクオブジェクトの作成が成功し、 m_pSink->SetObjectInfo(pMyApl, m_dwCookie) でSARIとの接続が完了したあとに行います。 ISRAPLインターフェースのAddCommandメソッドを使います。  ISRAPLインターフェースのポインタは、OnConnection関数の第1引数として SARIから渡されています。 HRESULT AddCommand( [in]long lCookie, // アドインを区別する一意な数字です。OnConnection関数の引数 // としてSARIから渡されたものです。 [in]long lInnerNumber, // DLL内のコマンド番号です。DLL内部で一意な正数を指定します。 [in]BSTR bsCommandName, // メニューやショートカットキーの割り当てで使うための、コマ // ンド名です。適当につけてください。空の文字列はだめです。 [in]VARIANT_BOOL bMenu, // メニュー登録するかどうかです。ユーザーが登録を望まない場 // 合は、FALSEにしてください。またここをFALSEにしておき、 // SetAddinMenuGroupメソッドによって、メニューをグループ化し // て登録することもできます。 [out, retval]long *lCommandNumber // コマンド登録が成功すると、ここに正数値が入って返ります。失 // 敗すると、0が入って返ります。この数値は、ツールバーを作成 // するときや、SetAddinMenuGroupメソッドが使います。 );  添付SariAddin.cppファイルのOnConnection関数に、コマンド登録の例をコメン トアウトして示しています。この例では内部番号1、2、3を有するコマンドを 3つ登録しています。  なをツールバーの作成や、コマンドに対するショートカットキーの設定方法に ついては、添付サンプルプログラムのOnConnection関数の該当部分をご覧くださ い。 * * * * 【コマンド実行通知の処理】   登録したアドインコマンドをユーザーが実行すると、SARIはシンクオブジェク トのOnAddinCommandメソッドを呼びます。このメソッドは二つのパラメータをも ちます。  第1引数はアドインDLLを識別するためのCookie値です。これが、OnConnection メソッドによって渡されているCookie値と一致するなら、イベントはあなたのプ ログラム宛です。一致しないなら、あなたのプログラム宛ではありませんから、 単にS_OKを返します。  第2引数はDLL内でコマンドを識別するための番号です。これは、あなたが AddCommandメソッドを呼ぶとき、lInnerNumberとしてSARIに渡した数値です。  したがって、コマンド実行通知を処理するためには、OnAddinCommandを下記の ようなものにします。 STDMETHODIMP CSink::OnAddinCommand(long lCookie, long lInnerNumber) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: この位置にインプリメント用のコードを追加してください if(m_dwCookie!=(unsigned long)lCookie) return S_OK; switch(lInnerNumber) { case 1: // ここで、あなたが内部番号1としたコマンドの実体を処理する。 break; case 2: // ここで、あなたが内部番号3としたコマンドの実体を処理する。 break; case 3: // ここで、あなたが内部番号3としたコマンドの実体を処理する。 break; ・ ・ ・ ・ // 以下、あなたがDLL内に作ったコマンドの数だけのcase文。 } return S_OK; } 【イベント通知の処理】  SARIは下記の時点で、それぞれイベント通知を発します。  このイベント通知はシンクオブジェクトの該当メソッドを呼ぶことによって行 われます。各メソッドとイベントの関係はつぎのとおりです。 OnNewInstance(); インスタンスが新たに作成されたときに呼ばれます。 OnPreOpen(); ファイルを開く直前によばれます。 OnPostOpen(); ファイルを開きおわった直後によばれます。 OnPreSave(); ファイルを保存する直前によばれます。 OnPostSave(); ファイルを保存しおわった直後によばれます。 OnCloseInstance(); SARIが終了されるときによばれます。 OnBroadcastMessage アドインから他のインスタンスへのメッセージです。  したがって、各時点でなにかの処理を行いたいのなら、該当するメソッドの内 部にコードを書いてください。処理する必要がないなら、メソッドはVCが作成し たスケルトンのままおいておきます。  なをSARIの自動マクロとの関係でいえば、各メソッドは、それぞれ、自動マク ロが走る直前に呼ばれます。 ======================================================================= 【付録A:VC++のATL系のバクについて】  VC++6.0のATLにはいくつか重大な問題があり、そのまま使うと、プログラム作 成環境では正常に機能するアドインも、その他の環境では正常に動作しない、と いうことが起こるようです。  これを回避するには、マイクロソフト社が無償配布しているVisual Studio 6.0 用サービスパック3以降を当て、VC++をアップデートしてください。  VC++5.0については詳しくは知りません。ただ、VC++5.0についても同様にサ ービスパック3が無償配布されています。 ======================================================================= 【付録B:添付サンプルについて】  アドイン集Aとして公開しているアドインプログラムのソースコードを、サン プルとして添付しました。稚拙な点も多々ありますが、どうぞ参考にしてくださ い。いずれも ATL + MFC で書いています。またシーケンスの処理にはSTLを使っ ています。  なを、作者はAl-mail32および電信八号のユーザーではありません。そのためAl -mail32, 電信八号の操作に関する部分では、改良の余地が多いと思います。  サンプル・プログラムの一部を改良したものを、あなた自身の責任において、 別プログラムとして公開配布していただいても結構です。ただその場合、プログ ラム名とCSariAddinのクラスIDは必ず変更してください。  各サンプルについて簡単に説明します。 <SrcAlml.lzh> AD_Alml.Dllのソースコードです。 3つの添付サンプルのなかで、このプログラムがもっとも単純です。 下記の諸点があります。 ・アドインコマンドの登録(AddCommand) ・ショートカットキーの取得と設定。(FindShortcutKey, SetShortcutKey,  GetCommandWithShortcutKey) ・メニュー登録(SetAddinMenu) ・ツールバーの作成(CreateToolBar) ・ISRMiscインターフェースの取得(CreateMiscObject) ・メール文字列の取得(GetMailString) <SrcDen8.lzh> AD_Den8.Dllのソースコードです。 下記の諸点があります。 ・アドインコマンドの登録(AddCommand) ・ショートカットキーの取得と設定。(FindShortcutKey, SetShortcutKey,  GetCommandWithShortcutKey) ・メニューグループの登録(SetAddinMenuGroup) ・ツールバーの作成(CreateToolBar) ・ISRFileインターフェースの取得(CreateFileObject) ・ISREditインターフェースの取得(CreateEditObject) ・ISRSearchインターフェースの取得(CreateSearchObject) ・ISRDisplayインターフェースの取得(CreateDisplayObject) ・ISRMiscインターフェースの取得(CreateMiscObject) ・メール文字列の取得(GetMailString) ・ファイルパスの取得(GetFilePath) ・ステータスバーへメッセージ表示(SetStatusBarText) ・ファイルを開く(OpenFile) ・ファイル保存(DoSave) ・SARIが桁折り中か(IsFormattingNow) ・全文書削除(DelAllDocument) ・更新・編集禁止属性の操作(IsDocModified, SetModified,  EnableEdit,SetEnableEdit) ・正規表現による検索・置換(GetSearchFlag, SetSearchFlag,  MakeDFATable, ExecSearchByRegExp, MakeReplaceTableForRegExp,  ExecReplaceByRegExp, CloseDFATable, CloseReplaceTable) ・通常の検索(ExecSearch) ・特定行までスクロールする(ScrollTo) ・他のインスタンスにメッセージを発する(BroadcastMessage) ・ポストオープンイベント(OnPostOpen)の処理。 ・ブロードキャストメッセージ(OnBroadcastMessage)の処理 <SrcFiles.lzh> AD_Files.Dllのソースコードです。 下記の諸点があります。 ・アドインコマンドの登録(AddCommand) ・ショートカットキーの取得と設定。(FindShortcutKey,  SetShortcutKey,GetCommandWithShortcutKey) ・メニューグループの登録(SetAddinMenuGroup) ・ツールバーの作成(CreateToolBar) ・ISRFileインターフェースの取得(CreateFileObject) ・ISREditインターフェースの取得(CreateEditObject) ・ISRMiscインターフェースの取得(CreateMiscObject) ・ファイルパスの取得(GetFilePath) ・ステータスバーへメッセージ表示(SetStatusBarText) ・文書がすでに開かれているか(IsAlreadyOpened) ・ファイルを開く(OpenFile) ・編集禁止の設定(SetEnableEdit) ・メインウィンドウのハンドルを取得(GetMainWindowHandle) ・他のインスタンスにメッセージを発する(BroadcastMessage) ・ポストオープンイベント(OnPostOpen)の処理。 ・ブロードキャストメッセージ(OnBroadcastMessage)の処理  なを、これらのソースの各プロジェクトファイルをVC++で初めて開いたとき は、「アクティブな構成の設定」が、「Win32 Unicode Release MinDependancy」 になるものと思われます。これらのプロジェクトはUnicodeでは作成されていませ ん。そのため、そのままではコンパイルエラーが出ます。非Unicodeの構成、たと えば、「Debug」や「Win32 Release MinDependancy」でコンパイルしてくださ い。