S-JIS[2007-10-03] 変更履歴

VC++ #import

#import <ファイル名>

COMのタイプライブラリを含んだDLL(やEXE等?の)ファイルを指定すると、その中のタイプライブラリを取り込んでくれる。
VB等で「参照設定」と言われている仕組みに相当していると思われる。


概要

#importが書かれているソースをコンパイルすると、拡張子がtlhtliのファイルが作られる。
そして、tlhが(#includeしたかのように)読み込まれる。(tliも自動的にコンパイルされる)

拡張子 説明? 内容
tlh タイプライブラリヘッダー? C++の通常のヘッダーファイルと同様に、クラスや関数や定数が宣言されている。
tli タイプライブラリの実装(implements)? C++の通常のソースファイルと同様に、クラスや関数が定義されている。

例えば「#import <shdocvw.dll>」により、プロジェクトのワークディレクトリ内(デバッグビルドなら「Debugディレクトリ」の下)に「shdocvw.tlh」と「shdocvw.tli」というファイルが作られる。


コンパイル時の警告

shdocvw.dllやmshtml.tlbをインポートすると、コンパイル時に警告が出る。(VC++2005)

#import <shdocvw.dll>
#import <mshtml.tlb>

warning C4192

'tagREADYSTATE' を自動的に除外し、タイプ ライブラリ 'shdocvw.dll' をインポートします

tagREADYSTATEが他の場所(ocidl.hの中)で既に定義されているので、shdocvw.tlhの中ではtagREADYSTATEを定義しない(除外する)。そのままインポートを続行する。
という意味。

この警告を消すには以下のような方法がある。


warning C4278

'FindText': タイプ ライブラリ 'shdocvw.dll' の識別子は既にマクロです。'rename' 修飾子を使用してください。

例によって変な日本語だが、要するに、shdocvw.dllの中で使っている「FindText」という識別子が 既に別の場所でマクロとして定義されている(commdlg.hで#defineしている)ので、rename属性を使って変更しろ、ということ。

この警告を消すには以下のような方法がある。


属性(修飾子)

#importでは、後ろに各種の属性を付与することが出来る。

属性は、スペース区切りまたはカンマ区切りで複数指定することが出来る。
また、行末に「\」を置くことにより、複数行にまたがって指定することが出来る。(#define等の普通のプリプロセッサ命令と一緒)

属性 説明
ネームスペース関連 no_namespace #import <shdocvw.dll> \
 no_namespace
ネームスペースの扱いを指定する。
shdocvw.dllの場合、
何も指定しないとネームスペースは「SHDocVw」になる。SHDocVw::IWebBrowser2のように使う。
ネームスペースが「IE」ならば、IE::IWebBrowser2のようになる。
no_namespaceならそのままIWebBrowser2と書いて使う。
rename_namespace #import <shdocvw.dll> \
 rename_namespace("IE")
識別子関連 exclude #import <shdocvw.dll> \
 exclude("OLECMDID","OLECMDF", \
 "OLECMDEXECOPT","tagREADYSTATE")
tlhの中では指定した項目の定義を行わない。
no_auto_exclude #import <shdocvw.dll> \
 no_auto_exclude
自動除外(exclude)を行わない。
すなわち、(他の場所で同名の定義があっても)tlhの中で定義を行う。
マクロ関連 rename #import <shdocvw.dll> \
 rename("FindText", "FindTextIE")
tlhの中の識別子を指定された名前に変更する。
左記の例だと、shdocvw.tlhの中でFindTextとなっていた箇所がFindTextIEになる。
auto_rename #import <shdocvw.dll> \
 auto_rename
tlhの中で使っている識別子が他の場所でマクロとして定義されている場合、tlh内の識別子の名前を自動的に変更する。
左記の例だと、shdocvw.tlh内のFindText__FindTextになる。
生成内容関連 named_guids   GUID構造体を使ったCLSIDやIIDの定義が生成される。
no_dual_interfaces   デュアルインターフェース(って何じゃ?)に関して、Invokeの方法が変わるらしい??
no_implementation   tliファイルを生成しない。
no_smart_pointers   スマートポインターの定義を行わない。
例えば、(IWebBrowser2に対する)IWebBrowser2Ptrが定義されない。
raw_interfaces_only   rawインターフェースのみ定義する。
例えばReadyStateというプロパティーに対し、get_ReadyState()という関数だけ定義される。(通常のCOMインターフェースなんだそうだ)
この属性を指定しない場合は、GetReadyState()という関数も定義される。(特殊なラッパーなんだそうだ)
バージョン関連 version #import "libid:〜" \
 version("4.0") lcid("9")
バージョン番号
lcid localization ID

属性は他にもあるようだ。VisualStudioのヘルプを参照。
VC++2005の場合、メニューバーの「ヘルプ(H)」→「カテゴリから検索(D)」でヘルプ画面を出し、URLに#import Attributesのものを入力すると属性一覧が表示される。(VisualStudioがインストールされている場合、InternetExplorerでそのURLを入力することでも表示される!ただしローカル以外からのリンククリックでは何も動作しない。VS2005がインストールされていない環境では、遷移はするが“無効なURL”のエラーになる

もしくは、MSDNで。


import専用ファイルの勧め

A.cppとB.cppがそれぞれ同じdllを#importしたとする。その際、#importの属性が違うと…

  1. A.cppの属性でtlhとtliファイルが作られる。
  2. その後、B.cppの属性でtlhとtliファイルが作られる。その際、作られる場所は同じなので、上書きされる。
  3. それをVisualStudioが検知し、「このファイルはリソースエディタ以外で変更されました。再度読み込みますか?」というダイアログが表示される。(“このファイル”というのは、tlhやtliのことだろう)
  4. 「はい」を選ぶと、A.cppによる再作成が行われ、上書きされる。これがVSに検知されて上記のダイアログが出る。
  5. 以下、繰り返し(苦笑)

したがって、別々のソースで同じファイルを#importするなら属性は同じにしないといけない。


しかし全ての#importに同じ属性を書くのは面倒(特に属性を色々付けていると)なので、#importするファイルは1つだけにして、他のソースからはtlhファイルを#includeするようにするとよい。
ただし、DebugビルドとReleaseビルドでtlhとtliファイルの作られる場所が違うので、場合分けする必要がある。

import.cpp:

#import <shdocvw.dll> auto_rename \
	exclude("OLECMDID","OLECMDF", "OLECMDEXECOPT","tagREADYSTATE")

#import <mshtml.tlb> auto_rename \
	exclude("_userHGLOBAL","wireHWND","wireHDC","_userHBITMAP","_userBITMAP", \
	"_RemotableHandle","_FLAGGED_BYTE_BLOB","IEnumUnknown" \
	"tagRECT","tagPOINT","tagSIZE", \
	"__MIDL_IWinTypes_0003","__MIDL_IWinTypes_0007","__MIDL_IWinTypes_0009")

A.cpp: B.cpp:

#ifdef _DEBUG
#include "Debug/shdocvw.tlh"
#include "Debug/mshtml.tlh"
#else
#include "Release/shdocvw.tlh"
#include "Release/mshtml.tlh"
#endif

…なんちゃって。素直に#import専用のヘッダーファイルを用意すればいい(爆)

import.h

#pragma once

#import <shdocvw.dll> auto_rename \
	exclude("OLECMDID","OLECMDF", "OLECMDEXECOPT","tagREADYSTATE")

#import <mshtml.tlb> auto_rename \
	exclude("_userHGLOBAL","wireHWND","wireHDC","_userHBITMAP","_userBITMAP", \
	"_RemotableHandle","_FLAGGED_BYTE_BLOB","IEnumUnknown", \
	"tagRECT","tagPOINT","tagSIZE", \
	"__MIDL_IWinTypes_0003","__MIDL_IWinTypes_0007","__MIDL_IWinTypes_0009")

A.cpp: B.cpp:

#include "import.h"

しかしexcludeに関して、なんでauto_excludeという属性は無いんだろう…? C4192の警告が出てるだけで、デフォルト動作は自動除外だからか?
ということは、#pragma warningで警告を抑制するだけでもいいのか。

import.h:

#pragma once

#pragma warning(disable:4192)
#import <shdocvw.dll> auto_rename
#import <mshtml.tlb> auto_rename
#pragma warning(default:4192)

…実際、excludeで全項目を列挙するのと同じ内容が生成された。全項目を列挙する苦労の意味は…?(苦笑)


参考


VC++ページへ戻る / 技術メモへ戻る
メールの送信先:ひしだま