勉強会でやってきました!前回色々進行がダメだったので仕切り直しと言う事で、最初に少し解説を加えたりしてみました。各自で解いてみるという形式で進行しました。その前に導入として:「System.Reflection.Emit」名前空間を覚えておくこと。System.Reflection.Emit.OpCodesクラスがあり、ここにILのオプコード(OpCode)が定義されているよ。OpCodeとはCLRが理解できる中間言語(バイトコード)OpCodeにはいくつか種類がある事。スタック操作・スタックマシンとは:CLRがバイトコード由来で動作するための基礎となる構造で、JVMのような類似技術もある。リアルなCPUにはあまり採用されていない事。実践して覚える今回も、前回と同様GitHubのテンプレートとなるコードを元に課題にチャレンジします。Part5では、インスタンスメソッドを扱い、その後のPartでクラス内のフィールドへのアクセス等を扱う予定でしたが、時間足らず。スライドは上げておくので、残りのPartを「忘れないうちに」取り組んでみる事をお勧めします。Languageを覚えることの意義ILの基本と言う事で2回も長時間セッションを行ったわけですが、ILを覚えたところで一体何の役に立つのか?と言う疑問を持つ方もいるかも知れません。ILをやっていて重要だなと思うポイントを2点挙げておきます。動的コード生成技術(式木・IL等)でもやりましたが、CLRには大別して「参照型」と「値型」という2つの型の相違があります。これらはCLR内でのメモリの扱われ方が違う上、IL上でも異なる扱われ方をされます。参照型の値と、値型の値と、値型が参照される場合、など、IL上でも正しく区別が必要です。これらの区別がC#やVB.netでも正しく出来ていない・苦手という人は多いと思いますが、ILでどのように扱うのかがしっかり理解できれば(しかもこれは単純な法則でもあるので、抽象的な概念などは不要で実は覚えやすい)、C#などでコードを書く場合でも、自信をもって書けるはずです。これが分かると、ボクシング(Boxing)・アンボクシング(Unboxing)のコストと、これらがいつ生じるのか、と言う事が正しく理解できます。気が付かないうちにこれらを発生させてしまい、パフォーマンスの低下を招くという事を回避できるようになるはずです。普段はコンパイラが自動的によしなにやってくれる、アセンブリメタデータの構造についての直観的な理解が得られます。C#の言語構造はこうなっているのか、の(すべてではありませんが)ILから見た姿が分かります。記述した型がどこでどのように使われるのか、あるいはメソッドの定義はどこからやってくるのか、ILとどのように関連付けされるのかが分かります。これが分かると、どんな場合にアセンブリを分割すべきなのか、あるいはリフレクションを使用して解決すべき課題なのかそうではないのか、と言ったことを判断できます。いった判断は、最終的にシステム全体の構造にも影響を与える可能性があるので、システム設計を行う上では無視できない要素の一つだと思います。学問で言えば本当に基礎的な部分に相当するので、これを習得するメリットが見えにくいのは事実ですが、覚えておいて損はないと思います。大体、ILなんて「単純」なので、難しそうで実は簡単なんですよ!(きわめて抽象度の高い概念を覚える事に比べたら、どんな人でも覚えれる可能性があります)。IL!!それではまた。MVPsが開催されました。そこで登壇してきた話です。MVPが登壇するコミュニティ主体勉強会です。MVPだけではなく、技術的な強みを持つ方も招きます。一年に一度のペースで、全国の各会場で同日に一斉に開催するので、トータルで見るとかなりの規模となってます。名古屋会場は約60名近い動員がありました。今年の名古屋会場は「オープンソースソフトウェア」が一つのキーワードとして、様々な角度から発表がありました。Core5」のリリースが迫っていることもあり、ここから展開して、現在のマイクロソフトのOSSに対する取り組みを概観出来たら良いなと考えました。ちなみにComCampは初登壇でした。1.0と名称変更」などと言う話が流れ、既にアカンやつやと言う流れにwwまあ、それはそれとして、今回の発表でどんな話を展開しようかと、日程的にギリギリまで考えあぐねてました。言うのも、マイクロソフトがと言うより、OSSと言う枠組みでなぜ業界がその方向に向かうのかと言うのは非常に幅が広くて、自分が考えている事だけでも1日で話が出来ないような大きな「うねり」です。その方面は、OSSコンソーシアムの吉田さんから丁寧に解説して頂いて、私の出る幕などない感じだったので良かったです。今までの.NET開発とこれからの開発が、OSSというエッセンスが加わることによってどんなふうに変わるのか、そしてマイクロソフトはなぜその方面に向かっているのか。技術方面で入口を紹介するのが良いかなと思いました。口でしゃべり続けてもアレなので、多分がりっちもデモで攻めてくるだろうと考え、私も全部デモみたいな流れでやってみることにしました(ちなみに、デモ主体は初めての試み)。大きくは、以下のような構成です。詳しくはスライドを参照してください。́Д`)まず、登壇前日までデモ機材の不調に悩まされ続けました。危なくてスライドをOneDriveに置いておけないので、OneDriveを切断してローカルディスクにコピーしてUSBメモリにも入れて準備。X201sのHyper-Vで動かす事にしました。所が、このX201sも最近は能力不足が露呈していて、Hyper-Vで仮想マシンを動かしているとどうにも重くて辛い。LibraOfficeでスライドを披露というのも妥協するしかないかなと思っていました。いう状況で「とどめを刺される」というのは、何度も経験しているので嫌な予感しかしない。VMを作り、本当の最悪はこれでデモしようと、2つ目のバックアッププランをちまちまと用意していました。以前よりも格段にパフォーマンスが向上しているのが体感できます。Codeのデモが出来ないという問題もあり、やっぱり最終手段として取っておくつもりでした。(この時点で11時ぐらい)それからスライドを一部修正したり、脳内で立ち回り方法を再構築したりなど、それはもう酷いプレッシャーぶりで。Codeのお話は大幅にカットせざるを得ませんでした。殆どの方は使ったことがあるとの事だったので、良かった。Ubuntuでも殆ど使用感は変わらないです。!きっと!!でも、それなりに楽しくやれました。Docker周りは語ってない事も多いのですが、事前準備でも結構面白かったです。Coreで依存性が大幅に緩和され、MacやLinuxでも動くようになるので、Dockerと組み合わせて是非遊んでみて下さい。そのための足掛かりになるような解説は行ったつもりです。2016」は、ソフトウェア開発手法として知られる「スクラム(Scrum)」に興味のある方、この手法を適用している組織の方、トレーナーの方などが集まる大きなイベントです(一般の方も参加できます)。私は認定スクラムマスターを2年ほど前に取っていますが、1年前のRSGTには参加できず、今回の参加を非常に楽しみにしていました。セッションの概要や内容はリンク先を参照してもらうとして、参加者の視点で、内容をちょっと紹介したいと思います。セッションスケジューラーはこちら。どのセッションも興味深く面白い内容だったのですが、去年末にCSPOを取ったことで、全体を通してちょっと見方が変わったかなという、自分自身への印象がありました。全部紹介できないので、勝手視点で勘弁してください。本当にどのセッションも素晴らしかったです。18スクラムマスターの役割を一人で背負いすぎて折れてしまったという点が、本当に共感できました。つらい、と言うだけで、物事はどんどん悪い方向に転がっていきます。アジャイルソフトウェア開発やスクラムの導入を検討している方は、一人で抱え込まずに協力してくれる人を探して「みんなでやる」と言うのが重要だと思います。スクラムによって(いや、本当はスクラムによらず)目指すことは、スクラムなどのソフトウェア開発手法を適用することではなく(それは手段)、企業がどんな価値を提供できるのかだと思っています。それはすごく大きな問題なのですが、それを「ひとり」で実現するのは、本当につらい事です。このRGSTや他のカンファレンス・勉強会、場合によっては外部コーチの助けを借りよう。́Д`)まだまだ課題はあるけれども、「学習する組織へ」「組織の文化へ」と、粛々と物事を進めていくのが大切だという話で結ばれていました。カンファレンス終わった後で少しお話しさせて頂いたのですが、物腰柔らかい方で好印象でしたが、本人は超熱い方だと自負していました。PLoP)を適用しよう、それを今整理中ですという話です。パタンとは何か?という話から、何故スクラムの取り組みをパタン化する必要があったのかという経緯を分かりやすく解説していました。スクラムパタンはWikiで編集中で、パタンについてのアイデアがある方は、是非参加して欲しいとの事。最低レギュレーションは「そのパタンを3回適用して成功した」だそうです。Sutherland」は、スクラムがこの世界に広まってからもう随分と時間が経っていて、新しい考えやアイデアをもっと取り入れて整理したいと考えています。スクラム自身のカイゼンですね。私はパタン(特にデザインパターン)について懐疑的だったのですが、その理由が「コンテキスト(文脈)」をはっきりさせた状況でないとパタンは有効に機能しない、という、ある意味当たり前の事に気が付いていなかった(無意識では知っていたので、何となく避けていて、いつもパタンに対してモヤモヤしていた)。それをサラッと明らかにして貰えただけで感動でした。パタンの使い方:ある状況における問題を解決できる。(状況=文脈)結果として現れる状況が、新たな問題を生むかもしれない。新たに生まれた問題には、別のパタンを使う。この前後関係(何かしかの想定される問題・パタン適用によって何が解決され、副作用としてどんな問題が想定されそうか)というのは、スクラムガイドにも殆ど載っていないため、主に書籍やセミナー・勉強会で経験知として積むしかないかなと思ってた(+パタン懐疑)のですが、パタン化によってデベロッパの事が直接分からなくても意思疎通ができるようになるのであれば、これは良い道具になるなと思いました。このような言語化の難しい課題なので、作業は大変困難であるようです。ダメパタン案や置き換え案は、(書いてあるメモを)たき火にくべてサヨナラしたというエピソードが良かった。19Sekiさんは、同じMSMVP(カテゴリ違い)で、このセッションはプロダクトオーナーよりの話です。そもそも私は自分の会社はあるもののデベロッパー寄りなので、CSPO(認定スクラムプロダクトオーナー)を受けたら、今まで考えてたつもりになってたPOや企業の事業とはどんなものかの片鱗みたいなのを味わったという経緯があります。そんなわけで、このセッションも楽しみにしていたのです。内容は、「サービス」やそれに対する「事前期待」とは何か?そしてそれをスクラムに当てはめた場合にどうなるのか?という話です。「サービスサイエンス」なる言葉も初耳で、そんな分野があるのかというのが第一印象でした。事前期待の対象・持ち方・持ち主や、顧客が求める対象を定義することで、その相関の中で何に対して注力すべきで、あるいはそうではないのかが明らかになります。この方面、しょっぱなから苦手全開である事を自覚せずにいられなかった。́Д`)重要なことは、サービスの成果とサービスのプロセスの組み合わせで顧客の満足性を考えた場合に、成果側は「根付きにくく」プロセス側が重要度が高いという、サービスを施された側からは当たり前なのに実は見落とされがちだと言う事(あるいは、無意識に避けているか?)でした。「価格.com」に掲載して競争する場合の例が非常に分かりやすかった。ここまでは本当の意味での事業戦略みたいな話なんですが、この流れは、スクラムチームの運営にも当てはめることが出来ますよという感じで展開。スクラムチームのアウトプットをサービスとして考えた場合、ステークホルダーが期待している「事前期待」とマッチしていない事はありませんか?あるいは、このような分析手法を持ち込むことで、一般的に言われている「プロジェクトマネージメント」が、当たり前品質の一部しか担っておらず、そこで努力を続けてもあまり実りはなく、チーム内で差別化品質と言う事に注目できるようになりますよ、という内容でした。素晴らしい、そして課題が増えました。遠いなーそれにしても、裏番も超強力で、全部聞けなかったのが非常に悔しい。Shimaseさん)」。後から概要聞いてみたら、やっぱりすごかったらしいです。Publickeyで記事になっていました。注目度高い!運営の関係者の皆さん、スピーカー・参加者の皆さんありがとうございました。来年も1月ごろに開催される予定です。募集は10月ぐらいかな?まだ参加されていない方、是非参加してみて下さい。CLR年末会で登壇してきました。CLRも第五回で、毎回参加の方と新しく参加の方で程よく盛り上がってうれしい感じです。ChalkTalkもそうですが、遠方からわざわざ参加して頂いた方もありがとうございます。来年も継続して行きたいと思います。今回のネタは結構前から決めていたものの、年末の忙しさの為に十分な時間が取れず、直前に方針を決めてバタバタしてしまいました。猛省...と言う事だけではないんですが、ちょっとセッションの進行を変えてみました。動的コード生成技術(式木・IL等)」と題してディスカッションを行ったのですが、今回は本会でその展開をしようと思ったのです。ILを説明したのでは、実際にILを使う具体的なシチュエーションでも提起できない限り、右から左へ流れていってしまうだけだと考えて、ハンズオンのような形式で進行してみました。Emitが可能なコードを一から書いていると、なかなか本題にたどり着けないため、GitHubに元ネタとなるコードを用意しておき、Emitコードから書き始めれられるようにしました。コードをILで動的に生成するヘルパークラスです。Disposeメソッドです。引数と戻り値を指定可能なメソッドを定義します。結果Console.WriteLine(result);}}}Emitterクラスは、System.Reflection.Emitの煩雑なコードを隠ぺいするために定義しています。課題の多くはMainメソッド内のラムダブロックのEmitメソッドをいじります。このコードはnetcore5向けでも実行可能にしてあります。課題はPart1~Part10まで用意し、はじめに少しだけ「スタックマシン」についての解説を行いました。引数の値をToStringして返すようにするGitHubのコードを軽く説明して、Emit本体のコードを課題に従って書き換えてもらいました。coder!”と言う文字列を返すだけのものですが、引数で与えられたintの値を文字列に変換して返すのが課題です。実はこれ、大変な罠が...やるべきことは、スライド通り大きく3つあります。引数の値はどうやって入手するかヒントに書いておいたのですが、「OpCodesクラス」を見ると、IL命令の一覧が確認できます。スタックに値を積む(プッシュ)するのは、「Ld~」で始まる命令であることも説明しました。OpCodesの一覧を見ていると、「Ldarg_0」命令が見つかります。これを使うと、引数の最初の値をスタックに積みます。「Ldarg」や「Ldarg_S」命令でもOKです。その場合は、Emitメソッドの引数にインデックス(0ですが)を指定します。staticメソッドの呼び出しメソッドを呼び出すには、「Call」命令を使います。その時、Emitする引数に、呼び出すメソッドの「メソッド情報(MethodInfo)」が必要です。これには、リフレクションを使います。Type.EmptyTypes);ToStringのオーバーロードは複数あるので、正しいメソッド情報を選択するために、Type.EmptyTypesフィールドを使って、「引数0」のオーバーロードを選択させています(このフィールドを使わなくても、0個の配列でもOKです)。typeofを使うことに対して、一部の方がモヤモヤしていた(チートっぽい)ようです。厳密に0からInt32のTypeを取得したことにならないのではないかとの事で、確かにその通り。typeofを使うと、C#コンパイラがコンパイル時にInt32を解決しようとします。実行時に0から取得する事も不可能ではないのですが、その話をするとまた話がずれていくので、今は諦めてもらう事に。そこで、「ILSpy」を使って、C#で書いた等価コードを逆アセンブルした所、引数をスタックに積む際に、「Ldarga」命令を使っていました。orzLdarga命令は、対象の値そのものではなく、その値を示す参照(ポインタ)をスタックに積みます。これは、対象の値が「値型(ValueType)」であり、Callでそのメソッドに遷移した先ではthis参照として扱われなければならないため、Ldarg命令ではなく、Ldarga命令を使う必要があったのです。Int32の場合は値型と言っても、なぜ参照化しなければならないのか、ピンと来ないかもしれません。hoge.Cは、123か3か?Console.WriteLine(hoge.C);Computeメソッドはインスタンスメソッドなので、内部でthis参照が使えます。this参照を使った場合、そのインスタンスは、呼び出し元のhogeとなるはずです。Computeメソッドから呼び出し元が保持するインスタンスにアクセスさせるには、文字通り「参照」が必要なのです。従って、ここではLdarga命令を使って、インスタンスへの参照を渡す必要があります。対象が参照型(クラス)であれば、Ldarg命令で問題ありません。NullReferenceExceptionがスローされたのは、本当に偶然のようで、真の原因は分かりません。足りない!!そんなわけで、このPart1を30分ぐらいでやるつもりだったのが、これだけで時間使い切ってしまいました。値型と参照型の違いは、実際にはPart6ぐらいでやってもらう予定だったのが、こんな所でやる事になったのが敗因...時間を延長して続けるかどうかという話もあったのですが、他セッションもあったため、次回に持ち越しとなりました。次回に同じ題目で再チャレンジします。スライドはその時まで正式公開はしません。次回も同様に参加する場合は、Part1の内容の復習をしておいてくだしあ。́Д`)でも楽しかったです。Model)についてのディスカッションを行ってきました。参加された方はCOM方面に強い方が半数近くいて(MVP4人、元MVP2人、中には現役で開発やってるという人も)、とにかくこれ以上ないぐらい強力なメンバーで、濃い議論が交わされました。遠方からの参加もありがとうございます。ChalkTalkバンジャーイこの記事では、内容を「要約」して記載します。図については内容を考えて起こしなおしました。NAGOYAさんのスペースをお借りしました。ありがとうございました。事前の洗い出しまず、各参加者のCOMに対してのスキルセットを「バリバリ解説OK」~「COMって何」のレベルで、かつ自分がCOMに対して抱えている課題、という切り口で、いつも通り付箋に5分で書いてもらって張り出しました。写真は横に集約した後の状態なので分かりにくいですが、上から「COMって何」~「バリバリ解説OK」の順で並べてあります。要約すると:COMの概念的なものへの疑問COMとWinRTとの関係COMの将来展望COMの実践的な実装方法スレッドアパートメントとは何かマーシャリングとは何かという内容が全体的に知りたいとことと認識できたので、順に掘り下げていきました。いう基本的な疑問です。メソッドの呼び出し規約の問題コンポーネントインスタンスの生存期間の問題インターフェイス型の認識の問題と言う問題の解決に集約されるという意見交換がなされました。処理系の問題とは、C/C++コンパイラ・リンカーが、それぞれの製品(例:VC++、gcc、BC++など)で、シンボル名の内部的な命名規約が独自仕様である事と、メソッドのエントリポイントが特定されたとしても、その呼び出し規約が異なる事がある、と言う事です。処理系によってシンボル名が異なる(特にC++において)問題は、シンボル名に引数の型情報を含ませることから発生します。このシンボル名の操作の事を「マングリング(Mangling)」と呼び、その仕様は処理系によって異なります。同じ処理系でもバージョンによっても異なる可能性があります。Walker」と言うツールがあります。これを使ってEXEやDLLを調べると、エクスポートシンボルの名前が分かります。このスクリーンショットでは「KERNEL32.DLL」のエクスポートシンボルを見ています。APIのシンボル名が見えます。シンボル名が普通に見える関数は、呼び出し規約が「stdcall」である事を示しています。対比する形で、「MSVCRT.DLL」を見てみます。赤枠を見ると、「exception」とか「badcast」とか読み取れる単語もありますが、何やらわけのわからない記号が沢山見えます。これがマングリングされた状態のシンボル名で、VC++のシンボル名規約に従って変換された結果です。いえば、実際のところこれがC++のどんなシンボルに相当するのかを実演しなかったのを思い出しました。symbol」を選択すると表示されます。見比べてみると面白いと思います)。このような、C言語で言うところの関数名だけでは表現できない、C++のオーバーロードやクラスメンバ関数、const関数などを、シンボル名だけで識別可能にするために、マングリング操作が行われます。このマングリング操作が、処理系依存(かつバージョン依存)なのです。6.0で普通に作ったDLLとVBで作ったDLLを直接静的にリンクすることは出来ません。他の処理系についても同様です。COMは、これらの処理系依存シンボルを使わず、COMのランタイムが提供する情報だけでクラスのファクトリを特定し、メンバ関数の位置はvtableと呼ばれる関数ポインタのテーブルの並びの整合性を担保することで、この問題を回避します。vtableの話はディスカッションでやらなかったですね。vtableはC++のvtableそのままで、仮想関数へのポインタのテーブルです。やってクラスを特定するのかは、レジストリのHKEY_CLASSES_ROOT配下の情報を使います。ATLでプロジェクトを新規に生成すると、プロジェクト内に*.rgsのようなファイルが配置されます。これはコンポーネントレジストラスクリプトと呼び、要するにregsvr32された時にレジストリのどこにどんな値を書き込むのかを表し、このスクリプトでCOMのクラス(coclass)が特定できるようにします。GUID)なのかという情報を含みます。COMのクラスのインスタンスが、いつまで生き続けるのかと言う問題です。.NETのインスタンスは、誰からも参照されなくなり、GCが回収する(場合によってはファイナライザーが呼び出され)と死んだ事になるのですが、C言語のようなアンマネージな世界では、「malloc」や「new」によってインスタンスが(ヒープに)生成され、「free」や「delete」によってメモリから取り除かれます。解放するfree(p);このような簡単な例であれば、確保して解放するというサイクルから、解放のタイミングの時点で正しく解放されることが分かります。処理その2ComputeStep2();このような処理があった場合、ComputeStep2の内部実装では、既にpが解放されていることを知る由はありません。ComputeStepは別の誰かが実装済みであり、これからメインの処理を書く場合、ComputeStep2の実行前にpを解放してはならない事はどうやって分かるのでしょうか?このような、インスタンスの生存期間の問題を解決する方法として、COMではクラス実装側に参照カウンタを持ち、このカウンタ値を監視することで不要になったかどうかを判定します。生存期間のカウンタは、IUnknownインターフェイスのAddRefによってカウントアップされ、Releaseによってカウントダウンされます。カウンタの初期値は1で、AddRefのたびにインクリメントされ、Releaseが呼び出されるとデクリメントされます。カウンタが0になると、自己消滅するように実装します。使用する側は、ポインタをコピーするときにはAddRef、使い終わったらReleaseするという規約を守る事により、生存期間が正しく管理されるようになります。インターフェイス型の認識インターフェイス型の認識の問題もあります。C++上でインターフェイスを示すポインタをキャストして、目的のインターフェイスへのポインタを取得しようとしたとします。static_cast<ICalc*>(pUnknown);このキャストが正しく解釈されるかどうかは、処理系に依存します。IUnknownをICalcにするにはダウンキャストが必要ですが、C++の世界ではコンパイラがその判断を行えるものの、COMの世界ではインターフェイスの情報しか存在しないため、正しくキャストできる保証がありません。そのため、キャストと言う処理自体も、IUnknown.QueryInterfaceメソッド呼び出しで解決します。reinterpret_cast<void**>(&pCalc));第一引数はインターフェイスID(GUID)で、これが必要なのはCOMの世界は.NETとは異なりリフレクションが存在しないので、インターフェイス型を一意に識別するIDが必要だからです。この場合のIID_ICalcは、ICalcインターフェイスのIDである前提です。QueryInterfaceメソッド内でキャストを実装することにより、処理系の範囲内で正しくキャストが行えます。返されるポインタはキャスト後のポインタであるため、呼び出し元は処理系に依存することなくキャスト出来たことになります。このようなメソッドを提供することで、処理系に(C++固有の)型を認識させる必要をなくし、互換性を維持します。結局、IUnknownの実装を通じて、どうやって処理系に依存しないでバイナリ互換性を維持するのかという事が、COMの中心にある概念であることを確認しました。COMのIUnknownとその実装はこんな感じだ!というのを、C#ライブコーディングで疑似実装して、参照カウンタの動きや、QueryInterfaceによるキャストの挙動を示して、内容の共有をしました。以下のコードは、クラスファクトリも含めて穴埋めしたコードです。(あくまで概念コードです。Disposeに近いが、メモリストレージも削除される。破棄処理を隠蔽することで、処理系依存を排除。CやC++にはリフレクションは無いので、本当はインターフェイスID(GUID)で判定する。キャスト処理を隠蔽することで、処理系依存を排除。newの実装を隠蔽することで、処理系依存を排除。CalcClassFactory();}もう既にかなりアレですが、まだまだ続きます。COMとWinRTWinRT(ストアアプリ・UWPのフレームワークとして使われるライブラリ)は、基礎がCOMで出来ていることは分かっていましたが、それ以上の理解は私の中になく、あんまり調べる意欲もなかったので、知りたかった部分です。全てのCOMクラスはIUnknownインターフェイスを実装しますが、WinRTのクラスは、IInspectableインターフェイスも実装します。COMの世界ではITypeInfoインターフェイスをタイプライブラリ情報を公開するのに使いますが、これのWinRT版のような位置づけです。WinRTでは型情報は.NETのメタデータをそのまま使用します(winmd)。この情報との対応付けにIInspectableインターフェイスを使います。したがって、ITypeInfoのように、情報の細部までトラバース可能なメソッドセットは持っていません。COMにおいてITypeInfoインターフェイスの実装は任意ですが、WinRTのIInspectableの実装は必須です。WinRTにおいてのもう一つの事情は、スレッドアパートメントの扱いです。WinRTでは、メインスレッドに相当するスレッドがASTAに属し、それ以外のスレッドはMTAに属するそうです。各アパートメント間の関係は、通常のCOMと同一とのこと。こんな話をすると、当然「アパートメントとは何か」みたいなところを避けては通れないけど、とりあえずは次の課題へ。COMの実践的な実装方法ようするに、どうやってCOMを書けば良いのかわからない(.NETとかVB6以外で)。Library)での実装方法をライブデモしました。2005の時だったので、2005を使ってデモ(こんなこともあろうかと、2005はインスコしてあるのだよ)。ATLは途中から、C#のような属性ベースの指定で楽に書けるようになったはずなのですが、結局私的には移行しなかったので分からず...新しいプロジェクトの追加で、「ATLプロジェクト」を追加し、「プロジェクト-追加-クラス」で、「ATLシンプルオブジェクト」を選択すると、COMをATLで実装するクラスのひな型が追加されるウィザードが表示されます。短い名前の所にクラス名を入れると、各ファイル名とか良しなに決定されます。問題は次のウィザード。ここのスレッドモデルとアグリゲーションの選択も、結構ディープだよね、ああそれとフリースレッドマーシャラーとかも。この説明をするには、やっぱりアパートメントを避けては通れないしデモが中断してしまうので、ちょっと横に置いておく事に。これでひな型が生成されたら、あとは「クラスビュー」のCOMインターフェイスのツリーから「追加-メソッド」とか「追加-プロパティ」とかやると、メンバを追加するウィザードが表示されて、メンバ定義を簡単に追加できます。手動で定義するとなると、IDLファイル・ヘッダファイル・ソースファイルを同時に修正しなければならないため、かなり面倒です。Studioが管理者権限で起動していないと、最後にエラーが出ます。これはregsvr32でDLLを登録しようとしたときに、コンポーネントレジストラスクリプトがレジストリを変更するためで、素直に再起動。ここに実装コードを追加してください。ここに実装コードを追加してください。S_OK;}テストするコードはC#で書きました。COM側のビルドとregsvr32が完了していれば、C#の参照設定でCOMコンポーネントが参照出来ます。"ABCX";Console.WriteLine(testClass.TestProp);}}これで無事、コンソールに”ABCX”が表示されました。WinRTでは文字列をHSTRING型で扱いますが、これはリテラル文字列をいちいちメモリ確保してコピーして...というコストを削減するためだそうです。それだとCLRの世界での文字列と同じように扱われる訳で、イメージは湧きやすい。APIなどでメモリを確保したり解放したりするので、コストは高いです。いつ解放されるのか上記テストコードで、AddRefもReleaseも呼び出していないのは?という疑問に対し、CLRの世界にはRCW(ランタイム呼び出し可能ラッパー)があって、これがAddRefとかReleaseを呼び出しているのだ、しかし、この例では使用後にすぐプロセスが終了してしまうので、果たして呼び出されるのか?という疑問があり、じゃあ、Marshal.FinalReleaseComObjectを呼び出せばどうだ、いや、それを呼び出すと挙動がおかしい、などの説があって、試しました。"ABCX";Console.WriteLine(testClass.TestProp);Marshal.FinalReleaseComObject(testClass);}}ATL側では、インスタンスの寿命が尽きる時(参照カウンタが0になったとき)、FinalReleaseメソッドが呼び出されます。true);}}すると来ましたFinalRelease。これだとインターフェイスポインタを直接弄った方が安心感があるよなぁと思わなくもない(真相は分からず)。一つ気が付いたことが。FinalReleaseでブレークを張ったところ、スタックトレースがこのように。これワーカースレッドから呼ばれてるよね、何でだ?と思ったらMainメソッドに、STAThread属性を付けていなかった。STAThreadを追加したところ、無事にメインスレッドから呼び出された(このCOMコンポーネントがワーカースレッドから呼び出されること自体に問題はないんですが、一瞬えっと思ったので気が付いた次第)。このスタックトレースも大概だなぁ。なる事は予測できるんだけど...さて、これの何が問題かと言う事は、やっぱりアパートメントの話に踏み込まないと説明できないよね。言う事で、そろそろ参加者の意識共有も出来たので、アパートメントを話をし始めました。アパートメントとは何なのか?アパートメントとは、スレッドとCOMのインスタンスをグループ分けする概念で、そのグループとは何なの?というディスカッションをしました。グループの種類は「STA(シングルスレッドアパートメント)」と「MTA(マルチスレッドアパートメント)」の二種類(あと、概念的にはメインSTA)があります。アパートメントの概念が難しいのは、用語の説明から入らざるを得ないからかなーと思います。このような図が一般的に説明に用いられますが、内部実装的にこうなっているわけではなく、あくまで概念的に分割される、という事も、難解なところかなと。混乱すると良くないので、内部もこの通りになっていると仮定してもいいんじゃないかと思います(実際は違うって事が分かるようになったら、もう説明も不要になってるはず)。シングルスレッドアパートメント一つのスレッドだけが特定のSTAに属します。新たなスレッドを作り、STAに属させると、図の通り新しいSTAが作られます。メインスレッドがSTAとなった場合は、特別に「メインSTA」と呼びます(実際には、プロセス内で最初にSTAとして初期化されたスレッドです)。.NETの場合は、メインスレッドであれば、Mainメソッドに「STAThread属性」を適用し、ワーカースレッドを作る場合は、Thread.SetApartmentStateメソッドで指定します。図のように、STAは他のアパートメントと(概念的に)分離されており、そこに属するスレッドやインスタンスは、別のアパートメントから直接参照することが出来ないという原則に従います。マルチスレッドアパートメントMTAは、プロセス内にただ一つだけ存在します。MTAに属するスレッドは複数存在できます。スレッドをMTAに属させる方法も、STAとほぼ同等です。Mainメソッドに適用する場合は、「MTAThread属性」を使用します。STAとアパートメント間の特性STAとMTAの最大の違いは、STAは「ウインドウメッセージキュー」に依存していると言う事です。今までのCOMの説明にはウインドウとかコントロールのようなユーザーインターフェイスの話は全く出て来なかったので、「えっ」てなってました。APIとか存在しない)、すべての操作を非同期的に処理するためには、メッセージポンプにメッセージを配信する形で協調的に動作させる(ノンプリエンティブマルチタスク)必要がありました。この頃に作られたアプリケーションは、マルチスレッドのように横から突然割り込まれてメソッドが実行されるような事を全く想定していません(スレッドがそもそも存在しないので)。今でいうところのモニターロックや排他制御がまったく不要だったのです。そのため、マルチスレッド環境で別のスレッドからこのような実装を呼び出すと、容易に破たんします。現在のユーザーインターフェイスも全く同様の構造なのです。異なるスレッドからウインドウ要素を操作したりする場合は、すべからく安全策をとる必要があります。FormsであればInvokeメソッドを使って簡単に呼び出しの「マーシャリング」を実行できます。COMが考案されたころには、そのような強力な道具立てがなかったのです。既存のマルチスレッド非対応コードがバイナリライブラリとして「変更不能」な状態で存在するため、互換性も担保する必要があります。この問題をフレームワーク(COMランタイムライブラリ)で隠蔽し、どうにかして安全性と実装の軽減を両立させたかったのだと推測されます。STAがウインドウメッセージキューに依存しているとは、STA内のCOMコンポーネントインスタンスを操作する場合は、このキューに一旦要求(メソッド呼び出し情報)を格納し、「協調的」に処理させると言う事です。STA内のスレッドであれば、同じSTA内のインスタンスは、キューを介することなく操作できます。メソッド呼び出し結果の返却も、キューに一旦結果を格納して受け取ります。このときのキューは、呼び出し側のキューを使います。このように、ウインドウメッセージキューを介在させることで、ウインドウメッセージ(ウインドウの移動やリサイズ・ボタンのクリック・テキストの変更など)にいきなり割り込むことなく、順番に規則的に実行されるので、マルチスレッド競合について考えなくても良くなる、というのが、STAに属する利点となり、同時に高いオーバーヘッドが欠点となります。ところで、STAとMTA間で呼び出しを処理する場合は、呼び出し側がMTAであれば、呼び出し時はSTAのキューを使いますが、処理結果はキューに入らず、直接元のスレッドに伝達されます。逆であれば、呼び出しがキューに入らず、結果は一旦キューに入ります。これは、MTAは特定のウインドウメッセージキューに紐づく事がないからです。したがって、ユーザーインターフェイスを含むCOMコンポーネントをMTAに属させると、クラッシュするなどの予期しない結果を引き起こします。MTAには複数のスレッドが同居出来て、MTA内のCOMインスタンスはすべて介在される事なく直接呼び出しが成立するので、ほぼコスト0で高速にメソッド呼び出しが行われます。これが、IISにホストされるCOMコンポーネントをSTAではなくMTAで動作させなければならない理由です(MTAにするとパフォーマンスが向上するらしい、という理由で、背景を考えずにMTAに属させるコードを書くと、間違いなくハマります)。API)すると、コンポーネントに指定された属性に従って、インスタンスが配置されるアパートメントが変わります。COMオブジェクトウィザードのスクリーンショットですが、ここで、「スレッドモデル」を選択可能です。このスレッドモデルの選択と、現在のスレッドがどのアパートメントに属しているのかによって、配置されるアパートメントが決定されます。昔、この組み合わせの表をどこかで見た記憶があるのですが、改めて書き出してみました(ただし、すべて検証しているわけではないので、間違っているかもしれません)。MTAこの表を眺めていると、何を選択すべきかが見えてきます。ユーザーインターフェイス(ウインドウメッセージを必要とする)を含むコンポーネントであれば、「アパートメント」を選択する。これには、俗に言う「ActiveXコントロール」も含まれます。その中でも特にメインスレッド(メインSTA)でのみ動作可能なコンポーネントであれば、「シングル」を選択する。(今やこのようなコードは想像しにくいかもしれません。グローバル変数に状態を持っていて、これを操作しているようなUIコンポーネントは、多分メインSTAでしか動作しません)アパートメントの影響を受けないコンポーネントであれば、「両方」又は「ニュートラル」を選択する。MTAにのみ必ず属させ、ハイパフォーマンスでローコストな環境を必要とするコンポーネントであれば、「フリー」を選択する。再度確認したのが、MTAに属させたからと言ってハイパフォーマンスになるわけではなく、マーシャリングコストを最小化可能であることがミソなので、ここで不用意に「フリー」を選択すると、アパートメントの選択同様ドツボにはまる事に注意が必要です。無難な選択肢が「両方(Both)」ですが、表の上では「ニュートラル」も同じとなっています。両方とニュートラルの違いは、両方とすると、メインSTA・STA・MTAのどこにでも配置出来るものの、配置されるとそのアパートメントに完全に紐づきます。対して、ニュートラルとは、本当のところは「どのアパートメントにも属さない」状態となります。この違いは、マーシャリングの動作に影響します。マーシャリングとはアパートメントの話は抽象的なので、もう少し実装に近寄ります。アパートメントの境界を超えるために、COMのランタイムによって「プロキシ・スタブ」という一種のファサードが自動的に介在し、メソッド呼び出しを自動的にメッセージキューに保存し、目的のSTAのスレッドでこれを取り出して実行する、という処理を行う事を説明しました。一般的にこのような操作を行う事を「マーシャリング」と言います。マーシャリングは、カスタム「プロキシ・スタブ」コードで完全に独自の実装を行うか、あるいは「COMスタンダードマーシャラー」を使うかを選択できます。カスタムマーシャラーは深くて闇で資料も少なく、これを自前で実装する事は殆ど無いと思います。どちらにしてもこの図のように、アパートメント境界の間に入って、メッセージキューの操作をしたり、スタックを操ってリモートメソッド呼び出しを成立させる役割を担います。呼び出し元は、カスタムマーシャラーが「何故か」目的のインターフェイスを実装したインスタンスに見えるので、アパートメント内呼び出しと全く同じように、インターフェイスメソッドを呼び出す事が出来ます。IUnknown.QueryInterfaceメソッドは、インターフェイスIDを受け取って、そのインターフェイスへのポインタを返します。言う事は、COMスタンダードマーシャラーが、実際には実装を用意していなかったとしても、IID_ICalcのようなインターフェイスIDが存在して、キャストも成立させたように見せかければ、この状況を作り出す事ができます。返されるインターフェイスポインタは、ICalcを動的に実装したスタブクラスです。このマーシャラーが一体いつ挿入されるのかが、背景を知っていないと分からないと思うため、その説明をしました。APIでインスタンスを生成するとき、呼び出し元のスレッドが属するアパートメントと、COMコンポーネントに指定された「スレッドモデル」に応じて、インスタンスが配置されるアパートメントが決定されます。呼び出し元スレッドのアパートメントと異なるアパートメントにインスタンスが生成され、配置された場合、呼び出し元はどうやってそのインスタンスにアクセス出来るのか?答えは、CoCreateInstanceが返すインターフェイスポインタが、「既にCOMスタンダードマーシャラーなどのファサードを示している」ので、呼び出し元は何も考えなくても良い、と言う事です。この図のように、MTAのワーカースレッドは、CoCreateInstanceが返すインターフェイスポインタを、本物の「ICalc」を実装したインスタンスだと思い込んでいます。その実態はマーシャラーであり、ICalcのメソッドを呼び出すとマーシャラーのメソッドを呼び出し、後は前述のとおり、マーシャリング動作が行われます。内部的にはここが難しい所で、一度マーシャラー経由のインターフェイスポインタを取得した後は、メソッド呼び出し引数や戻り値にインターフェイスポインタが含まれていれば、マーシャラーが自動的にそのインターフェイスポインタにもマーシャラーを挿入します。(pResultDataは何を指しているか?)GetResultメソッドの戻り値がIResultDataという、別のCOMコンポーネントインスタンスへのポインタである場合、その実態は呼び出し先のアパートメントに存在する可能性が高いです(必ずと言うわけではない)。その場合、戻り値として返されるインターフェイスポインタは、あくまで呼び出し先のアパートメントのインスタンスを示しているので、それがそのまま呼び出し元に返されても、その後が困ります(そのまま使うとマーシャリングが行われないので、アパート境界が守られず、酷い問題が発生する)。そのため、スタンダードマーシャラーは、戻り値のインターフェイスポインタを「マーシャリング」し、新たなマーシャラーを割り当て、マーシャラーのインスタンスへのポインタを呼び出し元に返却します。呼び出し元がIResultDataのメソッドを呼び出すと、裏ではマーシャラーを介してインスタンスを操作することになり、安全性が担保されます。呼び出し元はIResultDataがマーシャラーである事は知る由もなく、直接呼び出しているのと変わりません。これを実現するには、引数や戻り値に「インターフェイスポインタが含まれているかどうか」を認識出来なければなりません。CやC++のソースコード上では、インターフェイス型を指定するので(人間の目には)判別できます。コンパイル後のバイナリには、型情報が含まれないため、自動的に検出する事が出来ません。COMスタンダードマーシャラーが、ノーメンテで動作するには、型の自動判別が可能である必要があるのです。一つは、インターフェイス型のメタデータをタイプライブラリ(*.tlb)として読み取り可能になるようにファイルを配置するか、リソースとしてDLLに埋め込み、「プロキシ/スタブデータ」を提供することです。これにより、COMスタンダードマーシャラーは、引数や戻り値の型情報にアクセスし、それがマーシャリングの対象である事を認識できます。一つは、同じくタイプライブラリを提供する必要がありますが、引数や戻り値の型にVARIANT型を使う事です(プロキシ/スタブデータは不要)。VARIANT型は、格納できるサブタイプを指定して値を格納します。サブタイプの種類にハードコーディングされた制限がありますが、基本的なプリミティブ型やインターフェイスポインタなどを含むことが出来、何が含まれているのかを検知出来ます。VARIANT型を使うと、タイプセーフ性は低下します。カスタムマーシャラーを書いた場合は、自分でマーシャリングを行うため、タイプライブラリの準備やVARIANT型への依存は不要です。IDispatchインターフェイス経由でのみ公開されるメソッドの場合は、すべてVARIANT型である事を想定可能なので、タイプライブラリは不要です。自動マーシャラーを全く介さないというソリューションもあります。一つは「フリースレッドマーシャラー(FTM)」を集約する事です。詳細は省きますが、この場合はマーシャリングを自分で面倒見なければなりません。一つが、スレッドモデルを「ニュートラル」とした場合です。この場合もマーシャリングは自分で面倒を見る必要があります。マーシャラーを全く介さないと、メソッド呼び出しはダイレクトにCOMインスタンスのメソッドを呼び出す事になります。引数や戻り値に渡されるインターフェイスポインタも生ポインタであるので、アパートメントの境界を越えてアクセスして良いかどうかは全く分かりません。そのような場合に自分でマーシャリングを実行する必要がありますが、2つの方法があります。APIを使って、安全なインターフェイスポインタを取得する。́Д`)グローバルインターフェイステーブル(GIT)を使う。APIやGITを使うと、インターフェイスポインタを再取得したときに、マーシャリングが必要だと判断されれば、マーシャラーが生成されて、マーシャラーへのポインタが返されます。不要と判断されると、直接COMコンポーネントインスタンスへのポインタが返されます。回り回って元のアパートメントでインターフェイスポインタを取得すると、ちゃんと再計算されて、生ポインタが取得できた、ハズ...現在ではGITを使う方が簡単です。ATLを使う場合は、CComGITPtrクラスを使うと、非常に簡単にマーシャリングを行うことが出来ます。GITで言うところの「Cookieデータ」が、APIを使う場合の抽象的なデータに相当します。FTMやニュートラルというオプションがあるのかと言うと、お互いのCOMコンポーネントが何であるかが確実で、お互いに共謀出来るのであれば、異なるアパートメントであろうが、直接生ポインタを操作しても問題ない(と分かっている)はず、という非常に厳しい制約の元でマーシャリングコストを0にすることも出来る、ということです。ただでさえ複雑なスレッドモデルを正しく理解する必要があるので、FTMやニュートラルを使う事は、よほどの理由がない限り避けた方が良いと言えます(せっかくCOMのランタイムが色々頑張ってくれる機能を放棄することに近しいです)。COM)」は、延長上の技術でしかない事がわかります。今までの話は、プロセス内でのアパートメント境界を超える場合のマーシャリング処理ですが、プロセス間通信にも応用できますね?(アウトプロセスCOMサーバー・サービス)プロセス間通信が出来るなら、マシン間でも通信できるよね?(DCOM)DCOMとは、要するにそういう事です。マシン境界をまたぐ場合は、セキュリティのトピック(認証と認可をどうするかなど)が存在しますが、基本は全く変わりません。COMの将来展望(クロージング)時間が押してきたので、ほかにもトピックはあるのですがクロージングに入りました。COM」と銘打ったのですが、実は「隠された副題」がありました。requiem」です。COMの全盛期は2000年頃(つまりもう15年も前)と思っているのですが、まだまだいたるところで使われており、すぐにomitすることは当面出来そうにありません。プログラミング環境は完全に.NETが主体となり、C++でコードを書く際も、ライブラリのインフラとしてCOMを選択すると言う事は殆ど無くなりました。自分なりの答えはあったのですが、「何故COMは失敗したのか」あるいは「何故COMは継続してメジャー足り得なかったのか」と言う事を、参加者に聞いてみたかったのです。意見としては:設計思想は凄かった。「コンポーネント指向」として、処理系依存の排除やバイナリ互換性を維持する方法論、スレッド安全性をランタイムで担保する抽象性の高さ。「インターフェイス指向設計」に根差している。COMは実態のクラスに一切タッチしないので、インターフェイス分割設計と言う事に強く意識させられた。