none
自作拡張機能からのC++プロジェクトのProjectItemsの構成変更 RRS feed

  • 質問

  • 一昨日から ファイル、フォルダ、フィルターなどについてプロジェクトの構成を自動で制御するための拡張機能を作っています。 (ひとまず VS 2017 Community で作成中)

    ---------------

    ※拡張機能向けの質問はこちらで良いのでしょうか?
    ※また今後もし、英語圏の方が拡張機能に関する情報が多いかもしれなそうなため英語のフォーラムで聞いてみようとした場合とかは、 Visual Studio Development > Visual Studio Setup and Installation あたりでいいのでしょうか?

    ---------------

    現在の構造を調べるのは、少なくとも既定の状態だと、ProjectItem.Kindが

    VSConstants.ItemTypeGuid.PhysicalFile_string     >>>   ファイル (C++もC#も)
    VSConstants.ItemTypeGuid.PhysicalFolder_string     >>>  C#のフォルダ
    VSConstants.ItemTypeGuid.VirtualFolder_string       >>>  C++のフィルタ

    という対応で取得でき、C#の方のitem追加はProjectItems.AddFromFile, ProjectItems.AddFolderで出来ることを確認しました。

    しかし、対C++プロジェクトでは

     

     

    【Q1】

    ファイルの追加時に(細かいところを省くと)

    var item = project.ProjectItems.AddFromFile(filePath);
    item.Properties.Item("ItemType").Value = VSConstants.ItemTypeGuid.PhysicalFile_string;

    のようなコードを実行したとき
    ----------------

    System.Runtime.InteropServices.COMException (0x80020009): 項目の種類 '{6BB5F8EE-4483-11D3-8BCF-00C04F8EC28C}' は、このプロジェクト項目プロバイダーでサポートされていません。
       場所 EnvDTE.Property.set_Value(Object lppvReturn)
    以下略…
    ----------------

    のような例外が出ます。
    Item("ItemType").Value への設定時の例外ですが

    C#プロジェクトへの追加では同等のコードで、この例外は出ず、正常に追加できます。

    現状では、C++プロジェクト(Project.Kind が {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} )の場合には、上記

    item.Properties.Item("ItemType").Value = VSConstants.ItemTypeGuid.PhysicalFile_string;

    の部分は実行しないように切り分けていて、それでC++側にも例外なしでファイル追加できているようなのですが、この対応でいいのでしょうか?
    (追加後に構成を再取得した際は、上記設定をせずとも、追加したファイルのProjectItem.KindはVSConstants.ItemTypeGuid.PhysicalFile_stringで取得されているようです)

     

     

    【Q2】

    C#側でフォルダを追加するときはProjectItems.AddFolderで出来るようなのですが、C++向けのフィルタ追加的なメンバは見当たりませんでした。

    Generate C++ Filters

    という拡張機能のコードを拝見させて頂きましたところ、「プロジェクトファイル名.filters」のファイルをXmlWriterを使って自分で書いていっているようです。(まだざっと見なので細部を完全には把握はしていませんが)

    これは、この要件だと現状ではこうするよりほかにない、と考えるべきということで良いでしょうか?

     

     

    【Q3】

    そもそも、C++プロジェクトへの追加において

    ファイルの追加時は、「プロジェクトファイル」「プロジェクトファイル名.filters」の2ファイルが更新される    (<ItemGroup>の子要素が追加される)

    フィルタの追加時は、「プロジェクトファイル名.filters」のみが更新される  (<Filter>が追加される)

    という理解で良いでしょうか?
    他に影響範囲はあるのでしょうか?

    • 編集済み mr.setup 2020年6月26日 19:32
    2020年6月26日 19:29

回答

  • >Q1

    なぜProperties.Item("ItemType")を変更しようとしていて、何に変更しようとしているのですか?
    PhysicalFile_stringを設定するようなプロパティではないですよ?

    C#だとProperties.Item("ItemType")はC#ファイルのプロパティではビルドアクションに割り当てられていて変更可能ですが、VCだと変更可能な設定に割り当てられていないのでしょう。
    (ProjectItemは言語に依存しないがが、実装は言語依存)

    C#のビルドアクションに相当する、VCでのファイルの種類を変更したいのですか?
    そうであるならばProjectItem.KindがVCFileのはずなので、ProjectItem.ObjectをVCFileにキャストしてVCFile.FileTypeを変更しましょう。

    >Q2,Q3

    フィルターを拡張機能から操作するサンプル


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2020年6月26日 22:50
    • 回答としてマーク mr.setup 2020年7月2日 15:01
    2020年6月26日 22:49

すべての返信

  • >Q1

    なぜProperties.Item("ItemType")を変更しようとしていて、何に変更しようとしているのですか?
    PhysicalFile_stringを設定するようなプロパティではないですよ?

    C#だとProperties.Item("ItemType")はC#ファイルのプロパティではビルドアクションに割り当てられていて変更可能ですが、VCだと変更可能な設定に割り当てられていないのでしょう。
    (ProjectItemは言語に依存しないがが、実装は言語依存)

    C#のビルドアクションに相当する、VCでのファイルの種類を変更したいのですか?
    そうであるならばProjectItem.KindがVCFileのはずなので、ProjectItem.ObjectをVCFileにキャストしてVCFile.FileTypeを変更しましょう。

    >Q2,Q3

    フィルターを拡張機能から操作するサンプル


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2020年6月26日 22:50
    • 回答としてマーク mr.setup 2020年7月2日 15:01
    2020年6月26日 22:49
  • gekkaさん、ありがとうございます。

    【Q1】について

    >なぜProperties.Item("ItemType")を変更しようとしていて、何に変更しようとしているのですか?
    >PhysicalFile_stringを設定するようなプロパティではないですよ?

    あれ?本当ですね。
    だいたい上で自分でProjectItem.Kindって言ってるのになんでこっちに設定しようとしたのか

    Properties.Item("ItemType")に設定しようとする、というコード自体は、拡張機能自体ほとんど作ったことがなかったので(だいぶ前に1度だけ)どういう事だったのかを思い出すついでに、まずファイルの追加というのはどうやるのか、と検索した結果

    上記

    Generate C++ Filters

    を見る前に

    AddAnyFile

    を見させていただく流れで来ていまして、その中にファイル追加時に
    Properties.Item("ItemType").Valueに設定するコードが入っていたことが原因ですが


    AddAnyFileにおいてこのように使い方が間違っていたわけではありません。
    あちらでは、設定しようとしているitemTypeがnullならスキップされるコードになっていて、そして呼び出しもとを見るとデフォルト引数であるnullになっている箇所しかありませんでした。

    なので、間違いなく私が、読んでるときに何かの要因で誤解したままやっていただけです。

    ※基本方針として、dynamicのもので詳しいドキュメントを見つけてない場合は、とりあえず既存のものがどうなっているかデバッグ出力などでもして確認した方がいいかも、ですね

    というわけで調べてみたところ 

    .h  .cpp でも ProjectItem.Kindは

    PhysicalFile_string


    Properties.Item("ItemType")は

    ClInclude
    ClCompile

    None
    Compile
    EmbeddedResource

    等々


    ProjectItem.Object.Kindは

    VCFileになり得る


    ということが分かりました。

    ただ、最後の一点に関してはデバッグ時のブレーク中に「ローカル」ウインドウで動的ビューを展開して見ただけであって、肝心の「ProjectItem.Object → Microsoft.VisualStudio.VCProjectEngine.VCFile」のキャストは出来ていません。

    ()でのキャストだと例外が出て、変数名付きis演算子だとfalseになります。

    VS 2017だとちょっと状況が違うのでしょうか?
    ↓さっき見つけたばかりなのもありますが、こののページのSolutionsを見ても具体的に何をどうすればいいのかまだよくわかっていません
    Cannot convert EnvDTE.ProjectItem.Object to Microsoft.VisualStudio.VCProjectEngine.VCFile in VS 2017

    ただ、やはりgekkaさんがおっしゃいましたように、Properties.Item("ItemType").Valueに対して設定する必要はなさげなので、とりあえずこの設定コードは削っておく、でもいいのかもしれません。

     

     

    【Q2】【Q3】について

    ありがとうございます。

    なるほど、取っ掛かりとしてはProject.Object.Filtersから

    VCFilter


    を得られればあとはAddFilterでいけそう、ということですか。

    この場合、上記と同じ現象が出てキャストが出来ていません。

    foreach (VCFilter parentFilter in proj.Filters)

    に当たるところでエラー。ここを解決出来れば行けそうです。

    (※もしかして、とりあえずのテスト用にコードを書くにあたってデバッグを楽にしたかったので拡張機能用のプロジェクトじゃなくて、ひとまず普通のC#用プロジェクトを作ってそこに参照追加…とかでやっているのですが、そういう特殊なことをやってるから嵌るだけであって、実は拡張機能用プロジェクトで素直にやれば出来るかも…?

    どうせ後で拡張機能用プロジェクトには落とし込む必要はあるので、検証のついでにそろそろ本当に作ってコードを移し変え始めてみることにします。少々お待ちください)

     

     
    ※ちなみに、拡張機能関連のコードを書くにあたって、知識が不足している状態から必要な機能に適した方法を調べるためのおすすめのサイトとかってございますでしょうか?それとも一般的な決め手となるような場所は特になく、ケースバイケースの積み重ねとして調べていくしかない、ような感じでしょうか?

    • 編集済み mr.setup 2020年6月27日 2:29
    2020年6月27日 2:12
  • 実際イメージした拡張機能を作り込んでいくうえで色々思わぬ嵌りどころが多く、予定になかった部分で結構時間がかかりましたが、とりあえずFiltersからVCFilterを取得するのは、拡張機能用のプロジェクトでやったらできました。

    そして、やるべきことはほとんど網羅できましたので、あとは実際の運用上での細かい調整のみとなります。

    ありがとうございます。

    VCFileやVCFilterへの変換がなぜできなかったのかはよく分かりません。
    拡張機能作成用のプロジェクトは、デフォルトで色んなものが大量に設定されているので、何が直接的原因か特定は面倒そうです。ただ、今回は普通の方法ではできた、ということで分かるメリットが薄いと思うのでとりあえず良しとします。

    拡張機能のための情報は、公式としては

    https://docs.microsoft.com/ja-jp/visualstudio/extensibility/starting-to-develop-visual-studio-extensions?view=vs-2019

    このへんから辿っていくとある程度の情報は分かる様ですが、決定的なところで情報が不足していたりすることも多々あるの自分でカバーするためにはネット上で色々探すことは、やはり避けがたい気がしています。
    とはいえ、ある程度まで手掛かりがつかめたら後はじっくり調べてけばなんとかなることも多いかもしれない感覚もあるし、ひとまずはやるべきことについてかなり実現しつつもあるので、「ここだけ見れば何でもほとんどできる」というほどの場所が見つからなくても大丈夫かもしれません。

     

     

    今回の件について追加でわいている疑問点は

    【Q4】

    細かいところですが、「AddFilter, Filters, AddFile」はVCFilterだけでなくVCProjectにも可能で、プロジェクト直下に作るにはVCProjectの、それらの名称のメンバにアクセスする必要がある、と認識していますが

    VCProjectとVCFilterは別に同じ名前のそれらがあるだけで、おなじinterfaceを実装しているからとかいうわけではないうえ、Filtersだけを、VCProjectItemかなにかを追加できるC#的な直接的コレクション…みたいに取得できるのかどうか、出来るとしたらどうするのか、というのが現状ではよくわかっていません。

     

    かといってそのためだけにinterfaceを別途作るのも面倒に思ったため、現状ではVCProjectとVCFilterを
    「dynamic vcItemOwner」 みたいな引数で受けてそれに対して「AddFilter, Filters, AddFile」を呼んで、再帰処理…みたいにしているのですが、もうちょいマシな方法はあるのでしょうか?

     

    【Q5】

    また、AddFilterはCanAddFilterがfalseの時は出来ない・・・と確認しました。
    それでは、既存のフィルターを残した状態で、親のVCFilter(またはVCProject)から、目的の名前の「既存の」VCFilterの参照を得るための最適な方法は何なのでしょうか?

    現状では、ネット上で見つけたコードの通り

    VCFilter filter = null;
    
    foreach (VCFilter f in vcItemOwner.Filters)
    {
         if (フォルダ名が合致したら)
         {
              filter = f;
              break;
          }
    }

    みたいな取得法になっています。正直効率全然よくないと思いますが、これが妥当な方法なのでしょうか?
    (もっとも、他の所で工夫出来たり、そもそも現状の目的から行けばこれでもほとんどのケースで全く問題ないはずのパフォーマンスにはなってはいますが、一応…)

    ---------------------------------

    ※これらの件が分かるか、あるいは数日経過したら、今回の質問は解決済みとさせていただく予定ですが、全く別の部分での、拡張機能に関する細々とした疑問が、ここ数日で一気に湧き上がったので、おそらく別途質問を投稿させていただきます。

    • 編集済み mr.setup 2020年6月28日 11:42
    2020年6月28日 11:23
  • 【Q4】【Q5】

    に関しては、もし方法があるならまだ不明なものの、数日はとりあえずまったし、一応現状で困っているというほどではないのでひとまずはこれで良しとします。

    回答としてマークさせて頂きますが、新情報等ございましたら今後も受け付けます。

    2020年7月2日 15:01