今回のネタは、以下の方にしか役に立ちません。かなり狭いですね。
- Visual Studio 2005/2008上で ActiveX(COM)を使っている
- 自作ないしは結構更新が頻繁な、ActiveX(COM)を使っている
何をしたいのか?
要するに、何をしたいのか?と言うと、以下を行いたいのです。
VS2005/2008上でActiveX(COM)参照を使うと、プロジェクトファイル(*.proj)中に COMReference タグが作られ、COM参照のパラメータが設定されます。
例えば、以下は私の環境で、Windows Media PlayerをCOM参照した結果です。
<ItemGroup> <COMReference Include="AxWMPLib"> <Guid>{6BF52A50-394A-11D3-B153-00C04F79FAA6}</Guid> <VersionMajor>1</VersionMajor> <VersionMinor>0</VersionMinor> <Lcid>0</Lcid> <WrapperTool>aximp</WrapperTool> <Isolated>False</Isolated> </COMReference> <COMReference Include="WMPLib"> <Guid>{6BF52A50-394A-11D3-B153-00C04F79FAA7}</Guid> <VersionMajor>1</VersionMajor> <VersionMinor>0</VersionMinor> <Lcid>0</Lcid> <WrapperTool>tlbimp</WrapperTool> <Isolated>False</Isolated> </COMReference> </ItemGroup>
ビルド環境を変えたりActiveX(COM)をバージョンアップ更新すると、ビルドエラーが出て「COM参照を解決しろ」と文句を言われると思いますが、これは上記の「GUID(Guidタグの値)」の値が異なる為に、ActiveX(COM)が見つからなくなった為です。(もしかしたら、バージョン値もチェックしているかも)
ちなみに、このGUIDですが、プラットフォームSDKとかに含まれている OLEVIEW で確認する事が出来ます。(赤で記している箇所です)
01 posted by (C)wacky
GUID値の不一致が問題ならば、ビルド環境のActiveX(COM)のGUIDを、プロジェクトファイルの COMReference タグの Guid タグ値に反映させりゃいいじゃん?って事です。
今回は、そういう設定変更を半自動で行うスクリプト(wsf)を作った次第です。
プログラム本体
作ったスクリプトコードは、以下の通りです。
今回は、起動が軽めのWSHで作ってみました。はい。
ファイル名は、え〜、適当に com_chg.wsf 辺りでどうでしょう。
<job> <runtime> <description> VS2005/2008のプロジェクトファイルのCOM参照のGUIDとバージョン値の書き換えを行う 要するに、COMオブジェクトのGUID値を、ビルド環境に合わせる事で、参照の再設定を避けるのが目的。 </description> <unnamed name="--proj [ファイル名]" helpstring="変更対象となるVS2005/2008のプロジェクトファイル" /> <unnamed name="--name [COM参照名]" helpstring="変更対象となるCOM参照名。COMReferenceタグ、Include属性の値" /> <unnamed name="--file [COMファイル]" helpstring="COMオブジェクトを持つファイルを指定。--progのどちらかを指定せよ" /> <unnamed name="--prog [ProgID]" helpstring="COMオブジェクトのProgIDを指定。--fileのどちらかを指定せよ" /> <unnamed name="--eval [コード]" helpstring="COMオブジェクトに対するフィルタを行う。infoオブジェクトに対する操作を行う事" /> </runtime> <script> //----------------------------------------------- function P(msg) { WScript.StdOut.WriteLine(msg); } function change_value(name, obj, prop, value) { var ret = (obj[prop] == value) ? 0 : 1; var s = name + ": "+ obj[prop] + " -> "; obj[prop] = value; s += obj[prop]; P(" " + s); return ret; } //----------------------------------------------- // global変数宣言 var fso = new ActiveXObject("Scripting.FileSystemObject"); var xml = new ActiveXObject("MSXML2.DOMDocument"); xml.async = false; var xml_file_name = null; var include_name = null; var TLI = WScript.CreateObject("TLI.TLIApplication"); var info = null; // 引数解析 var argv = WScript.Arguments; for(var i = 0; i < argv.length; i++){ P(i + ":" + argv(i)); switch(argv(i).toLowerCase()){ case "--proj": i++; P("project file:" + argv(i)); xml_file_name = argv(i); xml.load(xml_file_name); break; case "--name": i++; P("xml include name:" + argv(i)); include_name = argv(i); break; case "--file": i++; P("load COM file:" + argv(i)); info = TLI.TypeLibInfoFromFile(argv(i)); break; case "--prog": i++; P("load COM object:" + argv(i)); info = TLI.InterfaceInfoFromObject(new ActiveXObject(argv(i))); break; case "--eval": i++; P("eval is '" + argv(i) + "'"); info = eval(argv(i)); break; } } // 設定変更処理 P("COM object info:"); P(" " + info.Name + ":" + info.GUID); P(" " + "Ver." + info.MajorVersion + "." + info.MinorVersion); P("XML info:"); P(xml.url); var node = xml.selectSingleNode("//COMReference[@Include='" + include_name + "']"); P(node.xml); var is_chg = 0; is_chg += change_value("Guid", node.selectSingleNode("Guid"), "text", info.GUID); is_chg += change_value("MajorVersion", node.selectSingleNode("VersionMajor"), "text", info.MajorVersion); is_chg += change_value("MinorVersion", node.selectSingleNode("VersionMinor"), "text", info.MinorVersion); P("change:" + node.xml); if(is_chg == 0){ P("--- no change complete ---"); WScript.Quit(0); } var new_file_name = xml_file_name; var old_file_name = xml_file_name + ".old"; if(fso.FileExists(old_file_name) == true) throw old_file_name + "が既に存在する"; fso.MoveFile(xml_file_name, old_file_name); xml.save(new_file_name); P("xml save to:" + new_file_name); P("--- complete ---"); </script> </job>
使い方
大体、以下の感じにして使います。
1.先ほどのスクリプトを、プロジェクトのフォルダに置きます。
2.プロジェクトのプロパティ→ビルドイベントを選択します。(C#プロジェクトの場合。VBの場合は コンパイル→ビルドイベント です)
02 posted by (C)wacky
3.「ビルド前に実行するコマンドライン」に、更新する目標のCOM参照と設定値を入れます。
Windows Media PlayerをCOM参照にしているなら、以下のように記述します。
cd $(ProjectDir) cscript com_chg.wsf --proj WindowsFormsApplication1.csproj --name WMPLib --prog WMPlayer.OCX --eval "info.Parent;"
4.ビルドします。
すると、ビルド前に 指定されたCOM参照のGUIDを比較し、不一致ならばプロジェクトを更新かけます。
技術的なちょっと話
ActiveX(COM)のGUID取得は、TypeLibInformation オブジェクトに含まれる、TLI.TLIApplication インタフェースを使っています。
欲しいよぅ、という人は↓以下からどうぞ。
- ファイル: Tlbinf32.exe: Tlbinf32.dll のファイルのヘルプ
http://support.microsoft.com/kb/224331/ja
GUID取得には、ProgID経由とファイル経由(Bindだろうなぁ)の二種類を用意しています。
ファイル経由の場合は、'--file'で指定します。自分が使っているActiveX(COM)の実体ファイルは何?という場合は、OLEVIEWでは 青でマーキングした所を見て下さい。
ProgID経由の場合は、'--prog'で指定します。自分が使っているActiveX(COM)のProgIDは何?という場合は、OLEVIEWでは、紫でマーキングした所を見て下さい。
なお、ProgIDの場合、そのままだと得られるGUIDはインタフェースの方で、ファイルの方じゃありません。なので、「--evel "info.Parent;"」を指定する事で、上位のGUID、即ちファイルのGUIDを得ています。
余談ですが、ここではCLSIDとか色々細かい用語の違いがある所、全てGUIDとしています。
確信的に言葉を誤用しちゃっています。正確には、ActiveXとかCOMとかOLEの技術書を読んでください。
プロジェクトファイルの操作は、MSXMLを使っています。まぁ、中身XMLですからね。
COM参照の探索は、XPathを使って求めています。いやぁ、便利ですね。