ふにゃるんv2

もとは、http://d.hatena.ne.jp/Wacky/

VS2005/2008のプロジェクトファイル中のCOM参照を、ビルド環境に合わせて自動更新する

今回のネタは、以下の方にしか役に立ちません。かなり狭いですね。

  • 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
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
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 インタフェースを使っています。


欲しいよぅ、という人は↓以下からどうぞ。


GUID取得には、ProgID経由とファイル経由(Bindだろうなぁ)の二種類を用意しています。
ファイル経由の場合は、'--file'で指定します。自分が使っているActiveX(COM)の実体ファイルは何?という場合は、OLEVIEWでは 青でマーキングした所を見て下さい。
ProgID経由の場合は、'--prog'で指定します。自分が使っているActiveX(COM)のProgIDは何?という場合は、OLEVIEWでは、紫でマーキングした所を見て下さい。
なお、ProgIDの場合、そのままだと得られるGUIDはインタフェースの方で、ファイルの方じゃありません。なので、「--evel "info.Parent;"」を指定する事で、上位のGUID、即ちファイルのGUIDを得ています。

01
01 posted by (C)wacky


余談ですが、ここではCLSIDとか色々細かい用語の違いがある所、全てGUIDとしています。
確信的に言葉を誤用しちゃっています。正確には、ActiveXとかCOMとかOLEの技術書を読んでください。


プロジェクトファイルの操作は、MSXMLを使っています。まぁ、中身XMLですからね。
COM参照の探索は、XPathを使って求めています。いやぁ、便利ですね。

最後に

ここ最近、ActiveX(COM)を使ったプロジェクトを使っていまして、どのビルド環境でも一発でビルドしたいのだけど、COM参照が邪魔して 何度も同じ繰り返しを余儀なくされていたんですね。


こんな感じのスクリプトで問題が解決できたので、今後は少し楽が出来そうです。