COM
VS2010とWindows7でCOMを作る時のハマり所
(2014. 9. 20)
久々にATL(COM)を作成する必要が生じ、以前の知識を元に作ってみた所、びっくりする程うまくいかない…。なんで〜〜(T_T)。そこで、ハマった所を列挙し、解決出来た所を記載していこうと思います。尚環境は以下の通りです:
・ Windows7 Professional (32bit) SP1
・ Visual Studio 2010 Professional SP1
@ errno.hが無い
至って普通にATLコントロールを作成してみます。
まずVS2010を立ち上げ、新規のプロジェクトを作成します。インストールされたテンプレートの中の[Visual C++]にある[ATL]を選択すると、[ATL プロジェクト]が出てきますのでそれを選択。名前はATLControlTestとしました。ソルーションを作った場所はC:\test以下です。これで[OK]をクリック。ATLプロジェクトウィザードが開きますのでそのまま[完了]をクリックします。
この段階でDebug構成でビルドしてみます。すると、
ハマり:errno.hが見つからない 1>c:\program files\microsoft visual studio 10.0\vc\atlmfc\include\atldef.h(19): fatal error C1083: include ファイルを開けません。'errno.h': No such file or directory
errno.hが見つからないというコンパイルエラーが出ました。
PC内のerrno.hを全検索してみるといくつか出てきました。関係ありそうなのは以下の4つ:
・ C:\Program Files\Microsoft Visual Studio 12.0\VC\crt\src\errno.h
・ C:\Program Files\Microsoft Visual Studio 12.0\VC\include
・ C:\Program Files\Microsoft Visual Studio 11.0\VC\crt\src\errno.h
・ C:\Program Files\Microsoft Visual Studio 11.0\VC\include
Visual Studio 12.0はVS2013、Visual Studio 11.0はVS2012の事です。という事はVS2010でのerrno.hが無い!ほぉ…びっくり。
この解決については以下のフォーラムにヒントがありました:
stackoverflow: Troubles with errno.h
http://stackoverflow.com/questions/3427792/troubles-with-errno-h
did you install the SP1 for VS2010, if yes, you can install Visual C++ 2010 SP1 Compiler Update for the Windows SDK 7.1 to resolve the issue, see http://blogs.msdn.com/b/vcblog/archive/2011/03/31/10148110.aspx
[訳]
VS2010のSP1をインストールしたかい?もしそうなら、Windows SDK 7.1用のVisual C++ 2010 SP1 Compiler Updateをインストールすると問題が解決するよ。詳細はhttp://blogs.msdn.com/b/vcblog/archive/2011/03/31/10148110.aspxを見てね
C++のコンパイラをアップデートする必要があるようです。上のリンク先を見ると、どうやらWindows SDK 7.1がインストールされている環境にVS2010 SP1を入れるとC++のコンパイラとライブラリがremove(削除)されてしまうようです。なので削除された物を再度インストールする必要があるとのこと。上のサイトにあるMicrosoftのダウンロードサイト(http://go.microsoft.com/fwlink/?LinkID=212355)からVC-Compiler-KB2519277.exeをダウンロードして実行です(VS2010は終了させておくこと)。
インストーラが立ち上がるので先に進んでいくとインストールが始まりそのまま終わります。終了後C:\Program Files\Microsoft Visual Studio 10.0\VC\includeの中を見てみると…あぁ、さっきまでいなかったerrno.hがいます!
再度ATLControlTestプロジェクトをVS2010で立ち上げてビルドしてみると無事に通りました。いきなりきっついハマり所です。
A ATLコントロールをVBに貼り付けられない
次にATLコントロールを作成してみます。VS2010のソルーションエクスプローラの[ATLControlTest]プロジェクト上で右クリックをして[追加]→[クラス]。クラスの追加ウィンドウにある[ATLコントロール]を選択し[追加]ボタンをクリック。するとATLコントロールウィザードが開きます。
コントロールの名前(短い名前)を適当にMyControlとしました。VB等でフォーム上で貼り付ける時のコントロール名がこれになります。入力するとその他のエディットボックス内も埋まっていきます。ただしProgIDは空のままです。とりあえずそのまま[次へ]と進みます。
オプションで[インターフェイス]が[デュアル]になっている事を確認します。これは関数を呼び出す時の方法を2つ提供するという意味です。一つはvtable(仮想テーブル)、もう一つはIDispatchインターフェイスを通した呼び出しです。VBなどはIDispatchを使って呼び出しするため必要となります。[次へ]と進んで行くと[表示]という項目内に[挿入可能]というのが出てきます。これにチェックを入れました。これを付ける事でOffice等でATLコントロールをOLEオブジェクトとして挿入できるようになります。
これで[OK]すると、
と出ます。リソースヘッダーを上書き…嫌な感じがしますがとりあえず[はい]にしておきます。
これで各種.hや.cppや.idlファイルが自動作成されますので、そのままビルドしてみると、通りました。ATLをビルドするとレジスタに作成したATLが登録されますのでVB上でも見る事が出来るはずです。そこでテスト用のVBプロジェクトを追加してみます。
ソリューションエクスプローラの[ソリューション'ATLControlTest']で右クリックをして[追加]→[新しいプロジェクト]。[他の言語]→[Visual Basic]から[Windowsフォームアプリケーション]を選択します。名前は「ATLControlTestVB」にしておきました。これで直ちにVBプロジェクトであるATLControlTestVBがソリューションエクスプローラに追加され、フォーム画面が出ます。
VBのフォームにVC++で作成したATLコントロールを貼り付けるには、そのコントロールをツールボックスに追加する必要があります。これは、ATLControlTestプロジェクトが選択されている状態でメニューにある[ツール]→[ツールボックスアイテムの選択]、開いたウィンドウのCOMコンポーネントから[MyControl Class]にチェックを入れます。パスを一応チェックしておいて下さい。
これで[OK]するとツールボックスに[MyControl Class]が追加されます。
さて、それをフォームに貼り付けて見ると…、
こんなエラーメッセージが表示され、貼り付けに失敗してしまいました。ハマり所再びです…(-_-;
B AxHostを生成できません
上のエラーをテキストとして列挙しておきます:
ハマり:AxHostを生成できません コンポーネント'AxHost'を生成できませんでした。エラーメッセージ:
'System.InvalidOperationException: コンポーネントを初期化できません。
場所 System.Windows.Forms.AxHost.DepersistControl()
場所 System.Windows.Forms.AxHost.ActiveAxControl()
場所 System.Windows.Forms.AxHost.TransitionUpTo(Int32 state)
場所 System.Windows.Forms.AxHost.CreateHandle()
場所 System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
場所 System.Windows.Forms.Control.CreateControl()
場所 System.Windows.Forms.Control.ControlCollection.Add(Control value)
場所 System.Windows.Forms.Form.ControlCollection.Add(Control value)
場所 System.Windows.Forms.Design.ParentControlDesigner.AddChildControl(Control newChild)
場所 System.Windows.Forms.Design.ParentControlDesigner.AddControl(Control newChild, IDictionary defaultValues)
場所 System.Windows.Forms.Design.ControlDesigner.InitializeNewComponent(IDictionary defaultVaules)
場所 System.Windows.Forms.Design.AxHostDesigner.InitializeNewComponent(IDictionary defaultVaules)'
これが何なのか…?調査です。
MSDNによると(MSDN: AxHostクラス)AxHostというのは.NET Frameworkが提供するクラスで、ActiveXコントロールをラップしてWindowsフォームコントロールとして使えるようにしてくれるクラスのようです。
こういうエラーが出るという事は、VC++で作成したCOM(ActiveXコントロール)がAxHost化されて無いという事なのかもしれません。ここで「あれ?んじゃ以前作ったCOMコントロールはどうだろう…」とふと思い、試しに以前のプロジェクトをVS2010でリビルドしCOMコントロールをレジストリに登録、VB上に貼り付けてみると…あらら、貼り付け出来ます。という事はこれはCOMを作成する際の問題のようです。そこで、うまくいく以前の物と新しく作ったMyControlとを比較してみました。
○ MyControl.hの比較
以下に以前のCOMコントロール作成時のヘッダーとMyControl.h内のクラス宣言部を比較してみます:
貼り付け出来ている方(CHoge) 貼り付け出来ていない方(CMyControl) // CHoge
class ATL_NO_VTABLE CHoge :
public CComObjectRootEx<CComSingleThreadModel>,
public IDispatchImpl<IHoge, &IID_IHoge, &LIBID_ATLHogeLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IPersistStreamInitImpl<CHoge>,
public IOleControlImpl<CHoge>,
public IOleObjectImpl<CHoge>,
public IOleInPlaceActiveObjectImpl<CHoge>,
public IViewObjectExImpl<CHoge>,
public IOleInPlaceObjectWindowlessImpl<CHoge>,
public ISupportErrorInfo,
public IConnectionPointContainerImpl<CHoge>,
public CProxy_IHogeEvents<CHoge>,
public IPersistStorageImpl<CHoge>,
public ISpecifyPropertyPagesImpl<CHoge>,
public IQuickActivateImpl<CHoge>,
#ifndef _WIN32_WCE
public IDataObjectImpl<CHoge>,
#endif
public IProvideClassInfo2Impl<&CLSID_Hoge, &__uuidof(_IHogeEvents), &LIBID_ATLHogeLib>,
public CComCoClass<CHoge, &CLSID_Hoge>,
public CComControl<CHoge>,
#ifdef _WIN32_WCE // コントロールを正常に読み込むのに Windows CE 上に IObjectSafety が必要です。
public IObjectSafetyImpl<CHoge, INTERFACESAFE_FOR_UNTRUSTED_CALLER>
#endif
{
...// CMyControl
class ATL_NO_VTABLE CMyControl :
public CComObjectRootEx<CComSingleThreadModel>,
public IDispatchImpl<IMyControl, &IID_IMyControl, &LIBID_ATLControlTestLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IOleControlImpl<CMyControl>,
public IOleObjectImpl<CMyControl>,
public IOleInPlaceActiveObjectImpl<CMyControl>,
public IViewObjectExImpl<CMyControl>,
public IOleInPlaceObjectWindowlessImpl<CMyControl>,
public ISupportErrorInfo,
public IQuickActivateImpl<CMyControl>,
#ifndef _WIN32_WCE
public IDataObjectImpl<CMyControl>,
#endif
public IProvideClassInfo2Impl<&CLSID_MyControl, NULL, &LIBID_ATLControlTestLib>,
public CComCoClass<CMyControl, &CLSID_MyControl>,
public CComControl<CMyControl>
{
...
ATLは多重継承ベースなので、沢山のインターフェイスが継承されています。で、赤文字のインターフェイスがMyControlに欠けているインターフェイスです。これらのインターフェイスはATLコントロール作成ウィザードが自動生成する物なので、もしかすると作成ウィザードですべき事をしなかったのかもしれません。では、ウィザード内の設定と上記赤文字のインターフェイスとの関係はどうなっているのでしょうか?そこで、新しいATLコントロールを作成し、MyControlで選択しなかった物を一つずつ追加してみます。
○ オプション:サポート:接続ポイント
ウィザード内の[オプション]にある[サポート:接続ポイント]にチェックを入れて追加してみます:
接続ポイントを追加した場合 // CATLControl2
class ATL_NO_VTABLE CATLControl2 :
...
public IConnectionPointContainerImpl<CATLControl2>,
public CProxy_IATLControl2Events<CATLControl2>,
{
...
太文字部分がMyContorlクラス宣言から追加された部分です。接続ポイントを追加するとIConnectionPointContainerImple<T>とCProxy_IATLControl2Event<T>が追加されるのが分かりました。
MSDN: IConnectionPointContainerImplクラスによると、このクラスは「アウトゴーイングインターフェイス」を沢山保持しているコンテナさんのようです。アウトゴーイングインターフェイス(外部インターフェイス)というのは、要はATLコントロールがクライアント(ATLを使う人)に公開しているメソッド(インターフェイス)の事です。ATLがこのインターフェイスを持っていれば、ATLを使う人は「あなたはどんなインターフェイスを持っていますか〜?」とインターフェイスIDを取得したり「んじゃそのIDのインターフェイスを下さい」と要求できるようになるのかな〜と想像します。
CProxy_IATL******Event<T>(***はコントロール名)はウィザードが自動生成する_IATL******Event_CP.hヘッダーファイルに定義されていました:
_IATL****Event_CP.h #pragma once
using namespace ATL;
template <class T>
class CProxy_IATLControl2Events : public IConnectionPointImpl<T, &__uuidof(_IATLControl2Events), CComDynamicUnkArray>
{
// 警告: このクラスはウィザードで再生成されることがあります
public:
};
IConnectionPointImplインターフェイスを継承していますが、空っぽです。IConnectionPointImplインターフェイスが先に説明したアウトゴーイングインターフェイスの本体なので、ATLに対してそういうインターフェイスを追加すると自動的にここに追加されていくんだろうなと思います。
では、これを追加したATLContorl2コントロールはVBに貼り付けられるのか?…んー駄目でした orz。これらインターフェイスは関係していないようです。
○ インターフェイス:サポートなし→サポートありへ
ATLコントロールのウィザードに[インターフェイス]というのがあり、そこにはサポートするインターフェイスとしない物とが列挙されています。VS2010でのデフォルトは以下の通りです:
おっと、矢印の所に先の貼り付けに成功したインターフェイスがごそっとあるではないですか!では、これらを[サポートあり]へ全部移して、さっきの[接続ポイント]も忘れずにチェックして、新しいコンポーネントを作成します。
出来たヘッダーを比較してみると…あ、継承しているインターフェイスが全部同じになりました(^-^)。
接続ポイント+上記インターフェイスを追加した場合 // CATLControl3
class ATL_NO_VTABLE CATLControl3 :
...
public IPersistStreamInitImpl<CATLControl3>,
...
public IConnectionPointContainerImpl<CATLControl3>,
public CProxy_IATLControl3Events<CATLControl3>,
public IPersistStorageImpl<CATLControl3>,
public ISpecifyPropertyPagesImpl<CATLControl3>,
...
public IObjectSafetyImpl<CATLControl3, INTERFACESAFE_FOR_UNTRUSTED_CALLER>,
{
...
さて、このATLControl3をビルドしVBに貼り付けて見ると…貼りついたぁー!!!
何と言う事でしょう、貼り付けに成功しました(^-^)。つまり、貼り付ける為に必要なインターフェイスが継承されていなかったのが原因だったようです。では、先程サポートありに追加したどのインターフェイスが作用したのでしょうか?追加した4つのインターフェイスを抜き差しして調べてみました。結果として必要だったのはただ一つ「IPersistStreamInitインターフェイス」でした![接続ポイント]も、貼り付けに限って言えば必要無しでした。
IPersistStreamInitインターフェイスはATLを使うクライアント(使う人)に対してそのATLが必要とするデータ(オブジェクトプロパティ)を提供し、またそのデータを更新して保存するためのメソッドを提供します(Save, Loadメソッド)。またオブジェクトのクラスID(クラスを判別する一意のID)を取得するGetClassIDメソッドも提供します。このインターフェイスが継承されていないと、クライアント側ではプロパティの操作が出来なくなってしまうという事です。
つまり、VBに貼り付けが出来なかったカラクリはこういう事なんだろうと思います。
VBにATLコントロールを貼り付ける時、フォームはまず貼り付けられたATLのインスタンス(オブジェクト)を自動作成しようとします。その為には貼り付けようとしているATLのクラスIDを取得する必要があります。その時にIPersistStream::GetClassIDメソッドを呼び出そうとします。しかし、このインターフェイスが無いとメソッド呼び出しに失敗してしまいます。貼り付けるインスタンスが無いと、それを包んでWindowsフォームで使えるようにするAxHostオブジェクトも当然生成が出来ない。そのために「AxHostの生成に失敗しました」というエラーが出た…。真偽は確認していませんが、少なくともIPersistStreamInitインターフェイスを継承すると、コントロールは貼り付けられるのは確かです(^-^)