Sorry, MSMVP will have to wait for another time.

(This post is edited after machine translated from Japanese.)

It’s been a while since I’ve posted anything.

I’ve been awarded the Microsoft Most Valuable Professional, or MSMVP for short, since 2015, but I’ve decided to decline it after 2021 term.

I would like to thank the MSMVP office and all the people who have helped me through this program. There will be no change in the community activities for the time being. In the near future, I will probably mainly write OSS because I like to write code. I have some projects on GitHub, so it would be nice if you could take a look.

I’ve been asked by someone who told me privately “if the `Center CLR` (My held community group) is going away?”, but it’s not :) We’re still active in the slack group. If you’re interested, feel free to check out the connpass page for directions (In Japanese)..

Here I would like to mention two good things about the MSMVP award, although they are short and almost a mere diary.

A shining thing

I’m a sucker for “hikarimono (shining thing)”, like jewels or jewelry, and I like the MSMVP trophy because it’s very well made.

This one is made of glass, and the blue-colored triangular pillar at the base and the flat monolithic stone monument-like part are perfectly fused together, making it shine and shine very brightly. This plaque can be fitted with a `Year ring` that you get every time you renew your award (just insert it from the side), and this will grow as the award year continues. I think the person who came up with this sculpture has very good taste.

I’m afraid that if I decline the MSMVP, this won’t grow anymore, which is a shame… I wonder if I can just get a Year ring every year :)

MVP Global Summit

The Global Summit is an event for MVPs held at MS headquarters in North America.

One of the perks of being an award winner is that you get to learn about new (unannounced) products and projects that are planned for the near future and discuss them with direct engineering team members, which is said to be a great benefit (another benefit is the MSDN subscription, but in my case I already had one, so…)

You have to pay for your own transportation, but they will pay for your accommodation (in a pre-arranged hotel).

So, for myself, I didn’t really feel that the advantages I mentioned above were that much of an advantage. The reason lies in the award categories.

My award category is Developer Technologies, which is about language processing systems and development environments, such as Visual Studio and C#/F#. Excepted Visual Studio, processing systems such as compilers and .NET runtime information now have public repositories on GitHub, where discussions are held daily. So the information available at Global Summit (in other words, NDA) was almost non-existent when I attended. So there was hardly anything that made me go to the Global Summit and mumble “Meow” (footnote). (Even if I had heard such information, mostly it would have been released a week later, so there was little benefit in hearing it now. And I was able to predict the general flow of events.)

As for why I’m introducing it here, I’ll never forget the memories of getting to know the MVPs who gathered at the Global Summit, and especially the F# area where they treated me well.

At the F# meetup held in the evening, I jumped into a place where I was the only Japanese person and forced myself to speak for about 10 minutes (I couldn’t speak). How is F# in Japan? I was really happy and enjoyed talking to them.

I was prepared to move to the hotel by myself on my way home at night, so I was trembling with fear that I might be kidnapped by the Uber driver to an unknown place and go back to Japan as a corpse (or maybe not anymore)… But he said, “Oh, I’m that way too, so you can take an Uber back with me!”

(To add, I had heard that the area around Seattle in North America was relatively safe, but even so, I knew there was a certain amount of risk involved in traveling alone at night.)

If it hadn’t been for that experience, I wouldn’t have ever thought of attending another overseas meetup, or even jumping into another overseas meetup. I’m not sure I would have thought of that.

Since I went to the Global Summit last term, I haven’t been able to contribute much to F#, and I still have my own regrets in that regard. I hope to be able to do something someday, so I follow up when I can, and in the OSS project I’m working on now, I always support F# with an awareness of it, unless it makes no sense.

Now that I’ve declined MVP (and even if COVID dies down), the thought that I won’t be able to participate in Global Summit anymore makes me shed tears, but when I get the chance (and if I have the budget), I’d like to go abroad on my own this time to meet the people I met, or go to other meetups.


Footnote: “Meow” is a word that MVP winners have no choice but to drop on Twitter and other social networking sites when they see or hear something exciting but cannot be disclosed under NDA. But this is frowned upon because it sounds like a kind of bragging to those who can’t participate. I did it once myself, and I’m sorry…

“パターンでわかる! .NET Coreの非同期処理” と楽屋ネタ

先日、“.NET Conf 2019 meetup in AICHI”を開催・登壇したので、その話をまとめます。

2つ目のセッションの話を先に

私は2つのセッションで喋りました:

1. “.NET Core 3の更新内容・紹介”
2. “パターンでわかる! .NET Coreの非同期処理”

最初の方はキーノートのまとめ的なものにしようと思っていたのですが、その話はまた後で。

非同期処理のセッションですが、今回はあんまり内部の技術的な話ではなくて、.NET Core 3.0とC# 8.0で.NETの非同期処理が一応の完成を見たと思っているので、改めて全体を網羅するような内容にしようと思ったものです。その上で、

* 度々非同期処理の誤用が話題に登ったり、辛しみのあるコードを目撃することから「こんな」記事を書いたりしていたのですが、もっと網羅的にまとめたいなーと思っていたので良い機会として共有しよう

というところを目標にしました。

スライド: パターンでわかる! .NET Coreの非同期処理 (SlideShare)

セッション50分でこの分量は無理ゲーなのはわかっていたので、スライドは後で読み返すことが出来るような構成にして、如何に喋りで補えるかに注意しました。が、セッションを聞きながら見ていない場合は、少し足りない(わかりにくい)部分があるような気はしています。セッションビデオと両方で補完してもらえればと思います。

なお、最近twitter上で非同期の事について話ししたものをいくつか置いておきます。この辺の話が今回のセッションをやりたいと思う動機につながっています:

上記の内容や過去の自分が書いた記事、構文的な書き方はわかるんだけど、具体にどう書いていいのかわからない… みたいなところから、ちょっとずつネタを拝借。

で、最初のセッション

最初のセッションの方は、参加した方には普通に楽しんでもらえたと思っているのですが、懇親会で舞台裏の話をしていたら、それをブログにまとめろとのお達しが… なので、普段、この方面の話は書かないのですが、楽屋ネタを書きます。

Scott Hanselmanこのセッションは、当初は私が全部.NET Core 3.0のキーポイントをまとめて1セッション構成するつもりだったのですが、水面下ではMSHQのScott Hanselmanが、日本のコミュニティ向けに何か喋ってくれるかもしれない、という話がもちあがり、それならその方向から構成を考えるために、決定するまで待つかと思っていました。

みなさんご存知の通り、大変に忙しいキーパーソンなので、この試みがキャンセルされるという事態も考えられましたが、関係者が頑張って調整に入っているようなので、ギリギリまで待つつもりでした。

ところで、コミュニティイベントを開催された方なら同意してもらえると思うのですが、イベント開催のためには以下のようなハードルをこなす必要があります:

* 会場の地理的なアクセスのしやすさの検討
* 会場収容人数
* 会場は有償か無償か。有償なら費用をどうするか
* プロジェクターや音声設備の有無・レンタル費用
* WiFiや有線LANなどの通信経路の確保
* 運営メンバーの募集と決定
* 登壇者の募集・登壇内容の調整
* ビデオ撮りあるいはストリーミングライブ配信するのかどうか(合意形成も含めて)
* チェックイン作業
* スポンサーを募集するのかどうか
* 集金や支払いなどの金回り
* ノベルティーなどのグッズ・抽選の有無
* 提供するおかしなど
* 終了後のハッピーアワーや懇親会の検討・場所の確保と誘導・支払い徴収

運営は本当に頭の痛い問題なのですが(これで折れてしまう方もいるので、私は自分が出来る範囲で出来ることしかやらない事を守るスタンスです)、登壇する側としても、セッションネタを考える場合に、上記の制限条件からネタ構成を考える必要も出てきます。例えば:

* 収容人数: 10名・30名・50名・100名かそれ以上、によって、セッションの構成(しゃべる内容や構成、進行方法)を変えたほうが良い。人数が多くなると、後ろの方の席の人は、スライドが読みにくいだとか、声が聞こえないとか、没入感が得られない、といった潜在的な問題が発生しそう。
* プロジェクターや音声設備: スライドの色校正(たいてい黒地だとハマる会場は多いが、パリッとした映像を出せる会場なら、逆に黒地が映える。動画を流す場合は音量に注意しないと、うるさいとか音が小さいとかの問題につながる。しかし、実際に会場で調整しないと思ったとおりには行かない(マイクとかミキサーなどの設備にも影響される)ので、セッションのネタにするのは結構なリスクとなる)
* WiFiや有線LAN: 参加人数が多いと、特別に設計されたWiFiアンテナなどを備えた会場でないと、話にならない事態になります。私はあまりオンライン向きのネタを扱わないので良いのですが、最近だとクラウドアクセスをデモの前提にする必要があったりして、非常にやりにくいでしょうね。逆に、ネットワークの信頼性が高い事が前提にできるなら、クラウドだろうがYouTubeだろうが、セッションの子道具としてバリバリ使えます。

まあ、細かいところを挙げるとキリがないのでこのぐらいで。つまり(人にもよりますが)、セッションネタ一つ決めるのにも、こういう要素が絡んできます。なので、登壇する側は事前にこれらの要素が早く確定していれば、それだけ登壇者や参加者にとって、実りのある内容に詰めていける可能性があるわけです。

で、冒頭の話につながるのですが、今回は会場を日立ソリューションズさん(HISOLさん)にお借り出来たのですが、その前にもいくつか候補があって調整していました。全ての会場が完全に同一条件なら、「まあ確保できるところでやればいいよね」と、問題の一つが片付くのですが、現実はそうじゃないのでヤキモキします。

結局、検討していた会場は確保できなかったのでHISOLさんに打診したところ、快諾得られたので良かったのですが、確定までに2週間前までかかったこともあって、なかなか会場を公表できませんでした。登壇者のみなさんごめんなさい。

こういう点で、名古屋は非常に苦労するというのはなかなか理解してもらえないところでツライです。東京に近いところは楽そうだなと思うし、もっと地方ではそもそも会場になる場所さえ無く、勉強会というものを想像するのも難しいと言うのが現実でしょう。

で、問題は上記の会場環境の話に繋がります。HISOLさんの会場では、残念ながらパブリックなWiFi設備を借りる事が出来ません(もちろん有線LANも)。すると例えば、クラウドベースのデモとかかなり厳しい(デモの悪魔に取り憑かれる可能性が高い)わけです。

言わずもがな、YouTubeを参照したりなど出来ず、オンラインで遠隔会場と連携しながらビデオチャットでセッションを進行するという、海の向こうでは割とポピュラーな手法も取れず… これはイベントの一体感もあるので、ある程度の規模のイベントではやりたくなることの一つです。

実際、もしかしたらHanselmanさんとTeamsで完全オンラインでの相互コミュニケーション、というのも検討の一つとしてありましたが、まあ会場的に無理なので、実現するとなったら有償で会場を確保するかということも考えなければなりません。

(そうすると参加費500円とかでは無理ですし、そもそもそんなギリギリで会場を予約できるか怪しいし、堅苦しい契約書をかわさなければならないですよ、個人で。請求や領収、今だと税金をどうするかみたいな話も出てくるかも)

なので、主催が本当に小規模な勉強会を小規模な会場でやる分には良いんですが、ちょっとでも規模が大きくなり始めると、非常に辛くなります。そこを踏ん張ってやるからには、それを超える動機づけが必要になるでしょう。私の場合は自分の技術的な知見をシェアする、まあ淡良くば技術ポートフォリオとして誰かの目に止まるかどうか、ぐらいで、これで食っていく気は全く無い(食える気がしない)ので、必然的に掛けられる労力もそのぐらいになります。

さて、そういうわけで、様々な軸での考えがあるため、これらをどこにバランスさせて、セッションのネタにするのか、というのは、真面目に考えるととんでもなく難しいです。今回のネタとして、実は以下のような移り変わりがありました:

ごく初期

.NET Confの開催より後に開くことを前提に、.NET ConfでMSHQで公開されるビデオをネタに同じような感じでローカライズバージョンをやればいいかな(鼻ホジ)

環境リッチな会場で開催できるか

.NET Confを拾い上げるにしても、様々なセッションから何をネタにするのかは、上で挙げたような要素が、特に会場確保によって大きく変わるので、まずはどこの会場が使えるのかが最大の課題。

第一としていた候補はダメ。あと2~3考えていたが、それらはどれも環境的に厳し目なため(WiFiが無いとか、ちょっと会場の母体の趣旨と合わないとか)、この時点でセッション内容についてはかなり幅が狭くなる。

Hanselmanさんがビデオメッセージを送ってくれる(あるいはリアルタイムで!)かも??という打診

すごい話だけど、運営側としては、完全に確定だからあとはやれるように組む、というのと違い、不確定だとどう動けば良いのかわからずツライ(それもギョムの合間を縫って行動する必要があるので)。早く決まって欲しい… 早く… 早く…

別の会場を打診

アテの一つは早々に辞退(上記の後者側)、もう一つを打診。

自分の登壇ネタを検討する

.NET Confのセッションを拾い上げるという案は、会場の自由度があるから考えられる手法であって、もうそれは無理なので、結局自分でネタをひねり出すしか無い。非同期の話をしたいから、まあ2枠って事になるかな。

で、問題はもうほかの登壇者さんの募集とか打診とかして枠が埋まりつつ合ったので、もしHanselmanさんの話が現実化する場合、どこの枠(時間帯)をその話にするのかを考える必要がある。内容がキーノート的なものなら、時間的にはやっぱり最初に喋って欲しいけど、そうでないならべつの時間帯でも良いわけだけど、果たしてオンラインで喋るとすれば、本当にJST(日本時間)に合わせてくれるの?(いや、そもそも無理ちゃうの?)

WiFiすら使えない環境でどうやって実現するの? 安定性は? Hanselmanの生の話が聞けるなら、みたいな期待で参加者が押し寄せて、出来ませんでしたテヘッ、みたいな事態を想定しなきゃいかんのか、とか…

非常にツライ…

正直なところは、予定が決まらなかったら、こちらから辞退することも視野に入れていました。自分のネタを考えているはずが、全体の進行まで気になり始めて、それどころじゃない感じ。全然集中できない…

気を取り直してネタ考案

最悪、キャンセルになって自分で全部仕切る(つまり2枠考えて喋る)ということを想定して準備。なんか色々頑張っても無駄に終わりそうで、それもつらい。 最初の方は、.NET Confのキーノートを読み込んで概要にまとめて、面白そうなトピックを実演を交えて喋る、みたいに構成するしかないかな(もはやキャパオーバーだけど、主催自分だから仕方ない)。

3日前の夜に、Hanselmanさんのビデオが送られてきた!!

もうこの時点では、8割方断る方向で心中決まっていて(でないと間に合わない)、手間が空いたときに断りのメッセージを送るしか無いか、と、散々たる気持ちでいたのですが、こっちからメッセージを送る前に向こうから来た。しかも、いきなりビデオが来た… マジか… もうこれで構成するしか無い(非同期のセッションをほぼ書き終えていたので、首の皮一枚つながった感じ)

ということで、20分のメッセージをまずはAzure Video
Indexerで字幕データを作り
、それを補正(わりと全自動では駄目な感じの字幕なので、手修正する)して、ffmpegで合成して英語版字幕付きビデオを作る(成果物はここ、但しビデオそのものは含まれていません。時間の関係で、訳を直したりタイムラインを調整したりと言ったところはまだ細部が荒いです。関係者はエンハンスして欲しいです、是非に)。

2日前

ビデオの内容を読み込み、字幕がアレだったことから日本語字幕化する事を考え始めた。でも、SRTファイルをそのままGoogle Translatorにかけるとフォーマットが壊れてしまうのと、センテンスがつながっていないのが原因でダメな翻訳になってしまうこともあって考えあぐねていた(Azure Video Indexerで日本語訳出せばいいじゃん、と思うかもしれません。機能的にはありますが、まあ、察してください…)

それと同時に、セッションをどう進行するかを考えていたけど、ビデオ流しながら補足を入れつつ、(50分枠なので)後半は小さいコードをライブで何かデモするか、そうだねぇ、あんまりどこにも話題にかかっていないgRPCとか良いかもしれない、と考えて、概要を調べる。

その後、字幕の調整のために、ぼーっとSRTファイルの中身を見てたら、これ、フォーマット簡単だよね? もしや自分でパース出来るのでは?と思いつく。そこで、あえてビデオを流さず、いきなりライブコーディングでパーサーを書き、Google Translator APIを使って日本語に翻訳(4G経由の通信なので、ここにリスクはあるが)して日本語字幕ファイルを出力するツールを書き、ffmpegで合成して、日本語字幕のついたビデオをその場でみんなに見てもらう、というのは、かなりエキサイティングじゃないか?

というのを思いついたので、パーサーを書き始める。パーサーは簡単だった。しかもここに非同期イテレーターを使っているので、.NET Core 3としてもふさわしい。

そして、Google Translator APIのメチャクチャ簡単で使いやすいこと!! しかも.NET向けのNuGetパッケージもあって、面倒くさそうなアカウントキーもどこからサインアップしてGetすればいいのか、わかりやすいドキュメント… なんてことだ!15分ぐらいで実際に翻訳出来た!! ええ、なんでGoogle API使っているのかって、自問してましたよ…

しかしここで字幕特有の難しさにブチ当たる。それは、1センテンス(日本語なら読点まで)が必ず1画面に収まるわけではなく、分割されたセンテンスに推測不能(一定時間ではない)時間タグがついて分割されている、ということなのです。

“My name is (ここで分割されている) Kouji Matsui.”

“私の名前は松井幸治です。”

果たしてどこで分割する? System.Stringで英文の割合で発生する分割点と同じ割合の文字で切るか? 例えば、46/54なら、「は」の後ろで丁度切れるかもしれない。しかし、こんなにうまくフィットするわけがなくて、普通は非常に読みにくい位置で分割されてしまう…

では、形態素解析するか?形態素解析すれば、助詞とか句読点位置を検出してそこで区切って、自然な感じの字幕に出来るのでは? えーやったこと無いぞ、今からは無理では… .NET実装はあるのか?と考えて検索したら、あった: .NET形態素解析エンジンNMeCab (SourceForge.jp)

NMeCabで検索すると、割と紹介記事があるのだが… ライブラリ更新されてない… net20とか古すぎる。そうか、じゃあNMeCabをベースにチャッチャと移植して、これはこれで別の機会にネタとしてまとめよう、と思いついて移植を始める。

1日前

NMeCabの.NET Coreへの移植が出来た… アンセーフブロック使ってたり古いリソース(Settingsとか)して、ちょっとハマる点があったけど、そのあたりを軽く手を入れ、net40, net45, netstandard1.3, netstandard2.0, netstandard2.1対応のNuGetパッケージ、しかも標準のIPADIC同梱でありながら、自分の辞書を使う事も出来るように構成可能にして公開しました。

まあ、気がつくともう翌日が本番なのに何やってるんだろう、みたいな…

で、日本語字幕を付けるコードの続きを書き始めたのですが、日本語字幕センテンスを割合で再分割してSRTのフレームにする部分が思いの外面倒で、疲れ切った頭では正しいコードをひねり出せず… もう寝る時間が無くなって本番の運営に支障をきたしそうなので、無念だけどここでピボットする事に。

MeCab.DotNetのことと日本語字幕ツールの話は、また後日のネタにする事にして、当日はHanselmanの英語字幕付きビデオを流してコメントを入れつつ、その後にHanselmanのデモと同じことを私が再現して、多分言い足りない部分を補う感じにすれば、丁度50分に収まるだろうと考えました。

(今回のセッションではなく)不特定の人に英語技術ビデオを見た後に感想を聞くと、なんか腑に落ちない顔をしていることがあります。英語や英語の字幕のせいでピンときていないのかも知れないし、ビデオでは簡単にやって見せているけど、周到に準備してるからじゃないの?というような疑念があるんじゃないかと思っているので、同じことを目の前でやって見せることで払拭したかったのです。

(Hanselmanさんのデモ自体は、上記のような不安要素は殆どなくて、押さえるべきところが押さえられていて流石だなと思っています。)

ポイントはそんなに多くなくて、実際やって見せればシンプルな話だと思っていて、目の前で動いているプロセスの状態を見たりとか、今まさにWindows側でビルドしたコードが、WSLのubuntu上でそのまま動きましたよね(間違いなく)、みたいなことじゃないかなと。

そういうわけで、これで50分のセッションを構成することにしました。この内容の利点は、資料が一切不要なこと。自分の喋りと進行をその場で工夫して乗り切ればOKだ。この時点でもう23時なので、他の手段は取れそうにない…

妄想

ということで、今回のイベントの裏がどうなっていたのかが何となくわかっていただけたかと思います。半分は運営全体の話、半分は個人的な事情によるものですが、こうしてみるとやっぱり運営に割ける時間が足りない、運営に手間を取られすぎる、ということが大きかったと思います。

コミュニティへの貢献は色んな方法が考えられます。私が一番して欲しいと思っていることは、ぜひ、自分の時事ネタでも興味のあることや知見でもなんでも良いので、発表・シェアしてほしいです。元々そういう場なので。

そして、コミュニティに物理的な(生々しい話をするなら金銭的な)貢献をして頂ける場合は、運営の裏舞台、特に発表とは関係のない諸々の作業(これは私の場合ですが、それぞれのコミュニティの事情を読み取って)の負担を軽減してもらえて、結果的に時間を削減出来ると、本当に助かります。時間を確保出来るようになれば、発表する内容をより吟味したり、面白く楽しい内容に出来るようになり、より良いコミュニティイベントを開催・継続できることにつながると思います。

(あえて挙げるとすれば、名古屋の場合はやっぱり会場ですかね。リッチな環境が整備された会場が提供されると、悩みの半分は解決します。場合によっては7割ぐらい楽になる感じ。今や募集とかはconnpassのようなサービスで出来るし、あとは当日そこに行って発表する事を考えるだけなので。いつも会場提供して頂ける団体や組織には、本当に感謝しています :)

Center CLR Try!開発 #3

今日は、「Center CLR Try!開発 #3」に参加ました。

クリエイトベース金山さんのスペースでやりました。冬場だからか、自分も含めて体調良くない人が居るようですね…

私はIL2Cのオブジェクト参照の追跡が足りない部分の修正と本を読む、MatsuokaさんはnanoFrameworkとMbed-Cli、neco3csさんはAngularの勉強、中村さんはNGK2018BのLT資料作成、加藤さんは非公開案件、という目標でした。

NGK2018B、今年はLT登壇エントリーしなかったので、気が楽ですわ :)


IL2C、昨日の時点で、 classとvalue typeのメソッドオーバーライド・オーバーロード・interfaceの暗黙実装・明示実装とそれぞれが複雑に絡んだパターンについてテストを書きまくって網羅した ので、長らく放置してた value type内のobjrefが追跡されていないので勝手にGCに回収されてしまう問題 を対処する、その方策を考えました。

今日中に対処できれば良いんですが、そう簡単でも無いことは分かっていたので、今日は算段をつけるという感じ。
例えば以下のようなコード: (ちょっとC#とIL混ぜちゃってますが、適当に読んでください)

public struct ObjRefInsideValueTypeType
{
    public string Value;  // <-- ここに動的に生成された文字列"ABCDEF"が保持される
    public ObjRefInsideValueTypeType(string value) => this.Value = value;
}

.class public IL2C.RuntimeSystems.ValueTypes
{
    .method public static string ObjRefInsideValueType() cil managed
    {
        .maxstack 3
        .locals init (
            [0] valuetype IL2C.RuntimeSystems.ObjRefInsideValueTypeType   // <-- IL2Cはこの構造体から上のValueフィールドを追跡できなければならない(がしていない)
    	)
        ldloca.s 0
        ldstr "ABC"
        ldstr "DEF"
        call string [mscorlib]System.String::Concat(string, string)
        call instance void IL2C.RuntimeSystems.ObjRefInsideValueTypeType::.ctor(string)

        // Release concat string from the evaluation stack
        ldstr "dummy1"
        ldstr "dummy2"   // <-- IL2Cはevaluation stackをCのローカル変数として確保するので、そこに参照が残っていると追跡されてしまうことから
        pop              //     別の参照を上書かせて追跡されないようにしてテストしている。
        pop              //     本来なら、こうしたとしても、value typeのフィールドは別の手段で追跡されなければならない(が今はダメ)

        call void [mscorlib]System.GC::Collect()
        ldloc.0
        ldfld string IL2C.RuntimeSystems.ObjRefInsideValueTypeType::Value
        ret
    }
}

で、ObjRefInsideValueType()を呼ぶとこういったケースのテストを行うのですが、構造体ObjRefInsideValueTypeType内のValueフィールドにセットされた文字列(System.String.Concatによって動的に生成されてヒープに配置された文字列のインスタンス)が、GC.Collect()によって回収されてしまうという問題です。

(文字列を結合しているのは、単なるリテラル文字列だとstatic constに配置されてしまってGCから無視されてしまうので、動的に生成させています。まあboxingされたインスタンスとか使っても良かったんですが)

とりあえずこのテストを書いて実行すれば、(IL2Cの問題によって) 文字列はGCに回収されてしまい、メソッドから返された文字列への参照 (IL2C上はポインタ) は無効な値を示していて、その後のアサートで刺さるからテストに失敗する、というシナリオです。

なんですが… 何故かテストが成功する…

で、VC++で実際にデバッグしてみると、問題なく無効ポインタで刺さったことが検出されます。IL2CのランタイムがこれをNullReferenceExceptionとしてスローする所に問題があって、想像してなかった死に方をしましたが(Unhandled exceptionはTODOで放置してたんだった…)

IL2CのテストはNUnitで書いていますが、実際には:

  1. テストコードのC#は、Roslynによって普通にアセンブリ(IL)になる
  2. アセンブリを普通に実行して、正しい結果が得られることをNUnitでアサートする
  3. アセンブリをIL2C.Coreに食わせてCソースコードを生成する
  4. Cソースコードをコンパイル(MinGW gcc4 32bitを使用)してネイティブの実行コードを生成する(今の所Windowsでやってるので、test.exe的なものが生成される)
  5. 実行コードを実際に実行する。内部ではテスト結果が同じようにアサートされる(この実行には.NET CLRは一切関与しない)。成功時は”Success”とstdoutに出しているので、それを確認

という感じで、テスト結果が完全に一致することを自動的に確認するようにしています。なので、今度はVC++とgccで結果が異なるという可能性がありえたので、VSCodeでデバッグ(C++ extensionでGDBを使ってデバッグできる)してみたのですが、こちらも正しく無効ポインタを踏んで死んでました。

んー何故なのか… と調べてるところで時間切れ。嫌な感じだな…


Try!開発は定期的にやる予定なので、興味があれば是非参加してください。

Center CLR Try!開発 #2

今日は、「Center CLR Try!開発 #2」に参加ました。

クリエイトベース金山さんのスペースを借りることが出来ました。募集が遅かったので人の集まりが悪かったのですが、参加者3人ともいろいろ吸収出来たようで良かったです。

私はIL2Cの例外処理、Matsuokaさんは次週のプレゼン資料、ayumaxさんはUnityで年末忘年会用の出し物を作る、という目標でした。


IL2Cは例外処理に取り組んでいます。
例外のうち、単純なcatch・複数のcatchの呼び分け・ネストしたcatch・rethrowと、それらそれぞれについてlocal unwind・global unwindは動いていて、finallyを変換可能にするのが今日の目標でしたがダメでした。

この図はIL内にtry-catch-finallyブロックの定義があった場合に、どのようなCのソースコードに変換すれば実現できるかを検討していたものです。ざっくり言うと、sjlj方式でsetjmpの戻り値によって例外ブロックに分岐して処理させ、finallyはそれらがbreakでブロック外に遷移したときに面倒を見る、という感じです。

(sjljの効率が悪いことは承知。移植性重視で、この作業が終わってからWindowsについてはSEHを使えるように、その先ではlibunwindを使うことも考えています)

悩んで解決できなかった部分が、最近C# 6で追加された例外フィルタ条件のサポートです。これはVB.net 1.0から使える(つまりはIL的には最初から実現可能だった)ものです。以下にC# 6で書いた例を示します:

class Program
{
    static string GetMessage(Exception ex, string banner)
    {
        Console.WriteLine(banner);
        return ex.Message;
    }
    static void Main(string[] args)
    {
        try
        {
            try
            {
                // C D B E F H
                throw new Exception("111");
                // C D [Unhandled exception]
                //throw new Exception("333");
            }
            catch (Exception ex) when (GetMessage(ex, "C") == "222")
            {
                Console.WriteLine("A");
            }
            finally
            {
                Console.WriteLine("B");
            }
            Console.WriteLine("G");
        }
        catch (Exception ex) when (GetMessage(ex, "D") == "111")
        {
            Console.WriteLine("E");
        }
        finally
        {
            Console.WriteLine("F");
        }
        Console.WriteLine("H");
    }
}

C#クイズとかで出てきそうですが、例外フィルタ式(コード上 GetMessage()を呼び出しているwhen句)の呼び出し順序を見て、のけ反る人もいるかも知れません。コメントに書いておきました。

例外がスローされるとき、

  1. 現在のスタックフレームの直近に存在するcatchブロックのうち、指定された例外型にキャスト可能なものがあるかどうかを探索し、なければよりスタックの底に向かって探索し続ける。
  2. 上記が存在する場合、更にフィルタ条件式があればそれを実行し、結果が満たされるかどうかを確認する。
  3. 1又は2が満たされてから、そのcatchブロックに遷移する。

という順序で実行する必要があります。ここで苦しいのは、スタックの底まですべての条件を確認し終えるまでは、catchブロックに遷移してはならない、という点です。したがって、まず、catch句の条件とwhenのフィルタ条件をすべてチェックする必要があります(満たされるものが見つかった時点で打ち切ることは可能)。

問題なのは、例外の型のチェックだけなら、IL2Cが自分の都合の良い判定Cコードを生成すればいいのですが、フィルタ条件式はILで指定されるので、ILを実行してフィルタ条件を満たしているかどうかを確認しなければなりません。しかし、catchブロックやfinallyブロックは、そのブロックを実行することが判明した時点でそこまでスタックを巻き戻し(sjljであればlongjmpする)ても問題ないことが自明ですが、フィルタ条件式の実行中は、まだスタックを巻き戻すことは出来ません。巻き戻してしまったらもう元に戻せず、それまでのスタック上の情報はすべて失われます(移植性を考えると、失われたと見なす必要があります)。

(上記のサンプルコードはまだ良いのですが、メソッドのローカル変数を使ったフィルタ条件式を考えてみると、問題の大きさがわかります)

これは頭を抱えました。

そもそも、例外フィルタ条件式を真面目に処理することをやめて、以下のように評価するという代替え案も考えられます:

try
{
    throw new Exception("111");
}
catch (Exception ex) // when (GetMessage(ex, "C") == "222")
{
    // Rethrow if additional condition is false
    if (!(GeMessage(ex, "C") == "222")) throw;
    Console.WriteLine("A");
}

しかし、この方法には2つの問題があります:

  • 例外ハンドラに遷移してから、更にrethrowで遷移するので、コストが高い。
  • 同じ型でcatchするブロックについて、一つのブロックで処理するように統合する必要がある。

11/11追記: これもやっぱり駄目ですね。スタック上位にfinallyブロックがある場合、このcatchに遷移した時点でfinallyを実行してしまいます。例外フィルタ式を使った場合と、実行順序が入れ替わってしまいます。

色々考えた結果、結局、以下の理由で、今その対処を行うことをやめました:

  • 現在(メソッドの)ローカル変数群のうち、オブジェクト参照を追跡する必要のある変数(GCがトラッキングして、不要なインスタンスではないことを確認する)は、そのアドレス群を “EXECUTION_FRAME”という構造体に記録し、リンクリストに追加することでGCがトラッキングできるようにしていますが、ここの実行効率が悪いことがわかっています。
  • 近い内にこれを改良する予定ですが、その際にこれらのローカル変数へのベースとなるポインタが用意に得られるようになるはずなので、フィルタ条件式のILを変換する場合には、このポインタ経由でローカル変数群にアクセス可能にすれば、スタックを一時的に巻き戻さなくとも式の評価が出来るはずです。
  • そのため、手順として、EXECUTION_FRAMEの改良が終わってから、改めて考えても良いかも?

と考えました。
なので、本日の目標としては、フィルタ条件式に対応しないが例外は処理できる、という感じに決定しました(そして、完成しなかった… 8割ぐらい?家に戻ったらfinishしたい)

ちなみに、.NET Framework CLRや.NET Core、monoとかはどうやっているのかという興味が出てきますね。自力でネイティブコードを出力してるので、如何様にでも出来るといえば出来ますが…

——-

Try!開発は定期的にやる予定なので、興味があれば是非参加してください。

Async deepdive before de:code と de:code振り返り

WP_20160523_21_05_51_Richちょっと遅くなりましたが、de:code 2016の前夜祭である、Japan ComCamp meets de:codeで登壇した時のスライドです。

1セッション15分と言う事で(ちょっとオーバーした)、スライドはSlideShareに載せてあり、ブログに載せるほどのものでもないのですが、折角なので載せておきます。

一般的に、async-awaitを使った非同期プログラミングを行う理由付けを、Windowsのメッセージポンプ(又はUIスレッド)との協調を行わなければならないという理由から展開しますが、このスライドでは純粋にパフォーマンスの観点から、非同期プログラミングの重要性にアプローチしました。つまり、「サーバーサイド」での非同期プログラミングの必要性を凝縮したものです。

主に「Async訪ねて3000里」や、過去私が解説してきた事の要約で構成されています。

某氏に「linuxではどうなのか?」と聞かれたんですが、linuxの非同期I/Oシステムコールがどうなっているのか次第だと思いますが、セッションで説明したテクニックを使って、殆どCPUリソース非依存な非同期ライブラリを作ることが出来るはずだと思います。

技術レベル:400~500


de:code振り返り on Center CLR

これもここに貼っておきます。先週、de:code振り返りな会を開催しました。裏番は「Rebirth Tohoku 2016」ですねつよい。

de:codeのセッションはそれはもう沢山あるので、あらかじめ関心のあったセッションをポストイットで貼り出してもらい、多いものから順に聞いてきた人が喋ってディスカッションする、というスタイルにしました。

私はChalkTalkと徘徊ばかりしていたため、あまり解説出来なかったのですが、赤間さんのセッションの解説(平内さん)が特にウケていました(その状況はどうなんだという疑問はある :)

教材は各セッションのスライドと動画(channel 9)です。特に今年からはchannel 9でストリーミングも行われるようになったので、去年も要望を出していたので非常にうれしい。これで何時でもセッションを再確認できるし、誰にでも資料のポインタとして使えるようになりました。

以下がディスカッションした対応セッションです。この他のセッションも概要レベルで解説などがありました。

  • CLT-001 今だからもう一度確認したい、クライアントテクノロジの概要と選択
  • DBP-019 りんなを徹底解剖。”Rinna Conversation Services” を支える自然言語処理アルゴリズム
  • DEV-010 エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法
  • CLT-016 拝啓 『変わらない開発現場』を嘆く皆様へ ~エンプラ系 SI 開発現場の「今」を変えていくために~
  • CLT-002 Windows 10 デバイスと UWP 完全解説
  • DEV-022 これから始める Xamarin ~環境構築から iOS/Android/UWP アプリのビルドまで~
  • SPL-005 オープンソースから見たマイクロソフト

de:codeへの感想ですが、一言で言うと「OSSで浮き足し立っている」かな。決して悪い意味ではなく、まだ定着するには時間が必要なんだと思います。殊更OSSを題材に挙げる期間が過ぎた時に、初めて評価されるんじゃないかなと。そうは言っても何もアピールしないのでは伝わらないので、MSは方向修正したよ!!って事を大々的に示している… ざっくりとですが、そんな気がしました。

それではまた。

真・ILのキホン – 第六回 Center CLR 勉強会

WP_20160319_11_10_36_Pro_LIILについての勉強会を、第六回 Center CLR 勉強会でやってきました!

前回色々進行がダメだったので仕切り直しと言う事で、最初に少し解説を加えたりしてみました。


導入

il1扱っているネタが極めて低レベルと言う事もあり、前回同様出題内容を各自で解いてみるという形式で進行しました。
その前に導入として:

  • 「System.Reflection.Emit」名前空間を覚えておくこと。
    System.Reflection.Emit.OpCodesクラスがあり、ここにILのオプコード(OpCode)が定義されているよ。
  • OpCodeとは
    CLRが理解できる中間言語(バイトコード)
  • OpCodeにはいくつか種類がある事。
  • スタック操作・スタックマシンとは:
    CLRがバイトコード由来で動作するための基礎となる構造で、JVMのような類似技術もある。リアルなCPUにはあまり採用されていない事。

を説明しました。


実践して覚える

今回も、前回と同様GitHubのテンプレートとなるコードを元に課題にチャレンジします。

果たして…今回はPart5まで到達出来ました!! Part5では、インスタンスメソッドを扱い、その後のPartでクラス内のフィールドへのアクセス等を扱う予定でしたが、時間足らず。スライドは上げておくので、残りのPartを「忘れないうちに」取り組んでみる事をお勧めします。

# 多分しばらくはILネタのセッションは無いですww


Intermediate Languageを覚えることの意義

ILの基本と言う事で2回も長時間セッションを行ったわけですが、ILを覚えたところで一体何の役に立つのか?と言う疑問を持つ方もいるかも知れません。ILをやっていて重要だなと思うポイントを2点挙げておきます。

  • 参照型と値型の明確な区別
    ChalkTalk CLR – 動的コード生成技術(式木・IL等)でもやりましたが、CLRには大別して「参照型」と「値型」という2つの型の相違があります。これらはCLR内でのメモリの扱われ方が違う上、IL上でも異なる扱われ方をされます。特に参照型の値と、値型の値と、値型が参照される場合、など、IL上でも正しく区別が必要です。これらの区別がC#やVB.netでも正しく出来ていない・苦手という人は多いと思いますが、ILでどのように扱うのかがしっかり理解できれば(しかもこれは単純な法則でもあるので、抽象的な概念などは不要で実は覚えやすい)、C#などでコードを書く場合でも、自信をもって書けるはずです。また、これが分かると、ボクシング(Boxing)・アンボクシング(Unboxing)のコストと、これらがいつ生じるのか、と言う事が正しく理解できます。暗に気が付かないうちにこれらを発生させてしまい、パフォーマンスの低下を招くという事を回避できるようになるはずです。
  • アセンブリメタデータの構造観
    普段はコンパイラが自動的によしなにやってくれる、アセンブリメタデータの構造についての直観的な理解が得られます。なぜC#の言語構造はこうなっているのか、の(すべてではありませんが)ILから見た姿が分かります。記述した型がどこでどのように使われるのか、あるいはメソッドの定義はどこからやってくるのか、ILとどのように関連付けされるのかが分かります。これが分かると、どんな場合にアセンブリを分割すべきなのか、あるいはリフレクションを使用して解決すべき課題なのかそうではないのか、と言ったことを判断できます。こういった判断は、最終的にシステム全体の構造にも影響を与える可能性があるので、システム設計を行う上では無視できない要素の一つだと思います。

学問で言えば本当に基礎的な部分に相当するので、これを習得するメリットが見えにくいのは事実ですが、覚えておいて損はないと思います。大体、ILなんて「単純」なので、難しそうで実は簡単なんですよ!(きわめて抽象度の高い概念を覚える事に比べたら、どんな人でも覚えれる可能性があります)。

Let’s IL!!

それではまた。

「.NET Core5から概観する、.NETのOSSへの取り組み」 – Nagoya ComCamp 2016 powered by MVPs

WP_20160220_10_04_02_ProNagoya ComCamp 2016 powered by MVPsが開催されました。そこで登壇してきた話です。

ComCampは、Microsoft MVPが登壇するコミュニティ主体勉強会です。MVPだけではなく、技術的な強みを持つ方も招きます。一年に一度のペースで、全国の各会場で同日に一斉に開催するので、トータルで見るとかなりの規模となってます。名古屋会場は約60名近い動員がありました。

今年の名古屋会場は「オープンソースソフトウェア」が一つのキーワードとして、様々な角度から発表がありました。私は表題の通り、「.NET Core5」のリリースが迫っていることもあり、ここから展開して、現在のマイクロソフトのOSSに対する取り組みを概観出来たら良いなと考えました。

# ちなみにComCampは初登壇でした。


「.NET Core5」じゃなくて「.NET Core 1.0」とか

タイトルとデスクリプションを決めろと言われて連絡し、公式サイトに掲載されてから3日後、いきなり「.NET Core5は.NET Core 1.0と名称変更」などと言う話が流れ、既にアカンやつやと言う流れにww

まあ、それはそれとして、今回の発表でどんな話を展開しようかと、日程的にギリギリまで考えあぐねてました。と言うのも、マイクロソフトがと言うより、OSSと言う枠組みでなぜ業界がその方向に向かうのかと言うのは非常に幅が広くて、自分が考えている事だけでも1日で話が出来ないような大きな「うねり」です。

で、あまり一般論みたいな話をしても、ComCampで私に期待される話でもない気もするし… 幸いその方面は、OSSコンソーシアムの吉田さんから丁寧に解説して頂いて、私の出る幕などない感じだったので良かったです。

今までの.NET開発とこれからの開発が、OSSというエッセンスが加わることによってどんなふうに変わるのか、そしてマイクロソフトはなぜその方面に向かっているのか。やっぱり技術方面で入口を紹介するのが良いかなと思いました。また、口でしゃべり続けてもアレなので、多分がりっちもデモで攻めてくるだろうと考え、私も全部デモみたいな流れでやってみることにしました(ちなみに、デモ主体は初めての試み)。

大きくは、以下のような構成です。詳しくはスライドを参照してください。

  • What is .NET Core? (.NET Coreって何?)
  • Disposable Infrastructure (破棄可能な環境)
  • Integrated Development Environment (統合開発環境)
  • まとめ

オリジナルスライドはこちら: NETCoreから概観するNETのOSSへの取り組み.pptx


裏話 – デモの悪魔

… やっぱり現れた、それも凶悪な奴が (;´Д`)

まず、登壇前日までデモ機材の不調に悩まされ続けました。.NET Coreがマルチプラットフォーム対応を目指している事を鮮明にしようと、UbuntuをインストールしたノートPC(非仮想マシン)を使って、スライドも含めて完全に非Windows環境でデモしようと準備していたのですが、UEFI BIOSにバグがあって64ビットUbuntuがブートしない…

# これは動いた気がしただけで、実はダメだった

デモでは.NET CoreとDockerを使うので64ビット版がどうしても必要(.NET Coreは現在はまだamd64のみサポート)で、前日まで横でトライし続けていたのですが、これ以上やってるとスライドも完成しないというギリギリ状態になり、泣く泣く断念…

それ以外にも、当日朝からOneDriveの同期が長時間やりまくり、何故か過去のフォルダ構成を再現しようと一生懸命ダウンロードしはじめる… もう危なくてスライドをOneDriveに置いておけないので、OneDriveを切断してローカルディスクにコピーしてUSBメモリにも入れて準備。

デモについては、ThinkPad X201sのHyper-Vで動かす事にしました。所が、このX201sも最近は能力不足が露呈していて、Hyper-Vで仮想マシンを動かしているとどうにも重くて辛い。LibraOfficeでスライドを披露というのも妥協するしかないかなと思っていました。

こういう状況で「とどめを刺される」というのは、何度も経験しているので嫌な予感しかしない。当日会場に入ってからも、念のためにAzure上でUbuntu VMを作り、本当の最悪はこれでデモしようと、2つ目のバックアッププランをちまちまと用意していました。

Azure VM、X201sでローカルで動かすよりも速い! 以前よりも格段にパフォーマンスが向上しているのが体感できます。しかし、Azure VMでは、Ubuntu Desktopを使い、VNC使えるようにするまで持っていかない限り、Visual Studio Codeのデモが出来ないという問題もあり、やっぱり最終手段として取っておくつもりでした。

が、奴はそれだけでは飽き足らず、X201sのHyper-Vをハングさせてボイコットの構えに (*´Д`) 勘弁してくれー (この時点で11時ぐらい)

それからスライドを一部修正したり、脳内で立ち回り方法を再構築したりなど、それはもう酷いプレッシャーぶりで。あとはリアルタイムで聞いて頂いた方ならお分かりの通りな内容でした… すいません。

# 特にVisual Studio Codeのお話は大幅にカットせざるを得ませんでした。幸い殆どの方は使ったことがあるとの事だったので、良かった。Ubuntuでも殆ど使用感は変わらないです。多分!きっと!!

でも、それなりに楽しくやれました。特にDocker周りは語ってない事も多いのですが、事前準備でも結構面白かったです。折角.NET Coreで依存性が大幅に緩和され、MacやLinuxでも動くようになるので、Dockerと組み合わせて是非遊んでみて下さい。そのための足掛かりになるような解説は行ったつもりです。

それではまた!!

Regional Scrum Gathering Tokyo 2016に参加してきました

WP_20160118_19_04_21_Pro_LI「Regional Scrum Gathering Tokyo 2016」は、ソフトウェア開発手法として知られる「スクラム(Scrum)」に興味のある方、この手法を適用している組織の方、トレーナーの方などが集まる大きなイベントです(一般の方も参加できます)。

私は認定スクラムマスターを2年ほど前に取っていますが、1年前のRSGTには参加できず、今回の参加を非常に楽しみにしていました。セッションの概要や内容はリンク先を参照してもらうとして、参加者の視点で、内容をちょっと紹介したいと思います。

セッションスケジューラーはこちら。

どのセッションも興味深く面白い内容だったのですが、去年末にCSPOを取ったことで、全体を通してちょっと見方が変わったかなという、自分自身への印象がありました。とても全部紹介できないので、勝手視点で勘弁してください。本当にどのセッションも素晴らしかったです。


「生き延びよう!強い組織になろう! – 迷わず行けよ 行けばわかるさ」(Takahiro Kaiharaさん)

スクラムマスターの役割を一人で背負いすぎて折れてしまったという点が、本当に共感できました。これはつらい。つらい、と言うだけで、物事はどんどん悪い方向に転がっていきます。アジャイルソフトウェア開発やスクラムの導入を検討している方は、一人で抱え込まずに協力してくれる人を探して「みんなでやる」と言うのが重要だと思います。

WP_20160118_14_12_25_Pro_LIスクラムによって(いや、本当はスクラムによらず)目指すことは、スクラムなどのソフトウェア開発手法を適用することではなく(それは手段)、企業がどんな価値を提供できるのかだと思っています。

それはすごく大きな問題なのですが、それを「ひとり」で実現するのは、本当につらい事です。このRGSTや他のカンファレンス・勉強会、場合によっては外部コーチの助けを借りよう。ぼっちいかん (´Д`)

まだまだ課題はあるけれども、「学習する組織へ」「組織の文化へ」と、粛々と物事を進めていくのが大切だという話で結ばれていました。

カンファレンス終わった後で少しお話しさせて頂いたのですが、物腰柔らかい方で好印象でしたが、本人は超熱い方だと自負していました。分かる気がしますw


スクラムパタン入門 / Intro to Scrum Patterns(Kiro Haradaさん)

スクラムの共有知として、パタン(パターン / Pattern Language of Programs / PLoP)を適用しよう、それを今整理中ですという話です。パタンとは何か?という話から、何故スクラムの取り組みをパタン化する必要があったのかという経緯を分かりやすく解説していました。スクラムパタンはWikiで編集中で、パタンについてのアイデアがある方は、是非参加して欲しいとの事。最低レギュレーションは「そのパタンを3回適用して成功した」だそうです。

スクラム提唱者の一人「Jeff Sutherland」は、スクラムがこの世界に広まってからもう随分と時間が経っていて、新しい考えやアイデアをもっと取り入れて整理したいと考えています。スクラム自身のカイゼンですね。良い事だと思います。

一方、私はパタン(特にデザインパターン)について懐疑的だったのですが、その理由が「コンテキスト(文脈)」をはっきりさせた状況でないとパタンは有効に機能しない、という、ある意味当たり前の事に気が付いていなかった(無意識では知っていたので、何となく避けていて、いつもパタンに対してモヤモヤしていた)。それをサラッと明らかにして貰えただけで感動でした。

WP_20160118_16_24_10_Pro_LIパタンの使い方:

  • ある状況における問題を解決できる。(状況=文脈)
  • 結果として現れる状況が、新たな問題を生むかもしれない。
  • 新たに生まれた問題には、別のパタンを使う。

この前後関係(何かしかの想定される問題・パタン適用によって何が解決され、副作用としてどんな問題が想定されそうか)というのは、スクラムガイドにも殆ど載っていないため、主に書籍やセミナー・勉強会で経験知として積むしかないかなと思ってた(+パタン懐疑)のですが、パタン化によってデベロッパの事が直接分からなくても意思疎通ができるようになるのであれば、これは良い道具になるなと思いました。もっとも、このような言語化の難しい課題なので、作業は大変困難であるようです。ダメパタン案や置き換え案は、(書いてあるメモを)たき火にくべてサヨナラしたというエピソードが良かった。


Customer Expectations Management of Scrum スクラムにおける事前期待のマネジメント(Mitsunori Sekiさん)

Sekiさんは、同じMSMVP(カテゴリ違い)で、このセッションはプロダクトオーナーよりの話です。そもそも私は自分の会社はあるもののデベロッパー寄りなので、CSPO(認定スクラムプロダクトオーナー)を受けたら、今まで考えてたつもりになってたPOや企業の事業とはどんなものかの片鱗みたいなのを味わったという経緯があります。そんなわけで、このセッションも楽しみにしていたのです。

スライドはこちら。内容は、「サービス」やそれに対する「事前期待」とは何か?そしてそれをスクラムに当てはめた場合にどうなるのか?という話です。特にいきなり「サービスサイエンス」なる言葉も初耳で、そんな分野があるのかというのが第一印象でした。事前期待の対象・持ち方・持ち主や、顧客が求める対象を定義することで、その相関の中で何に対して注力すべきで、あるいはそうではないのかが明らかになります。

この方面、しょっぱなから苦手全開である事を自覚せずにいられなかった。これは非常にまずい事が可視化された! (;´Д`)

重要なことは、サービスの成果とサービスのプロセスの組み合わせで顧客の満足性を考えた場合に、成果側は「根付きにくく」プロセス側が重要度が高いという、サービスを施された側からは当たり前なのに実は見落とされがちだと言う事(あるいは、無意識に避けているか?)でした。「価格.com」に掲載して競争する場合の例が非常に分かりやすかった。

servicequalityで、ここまでは本当の意味での事業戦略みたいな話なんですが、この流れは、スクラムチームの運営にも当てはめることが出来ますよという感じで展開。

この図に身に覚えのある人は多いと思いますがw スクラムチームのアウトプットをサービスとして考えた場合、ステークホルダーが期待している「事前期待」とマッチしていない事はありませんか?

あるいは、このような分析手法を持ち込むことで、一般的に言われている「プロジェクトマネージメント」が、当たり前品質の一部しか担っておらず、そこで努力を続けてもあまり実りはなく、チーム内で差別化品質と言う事に注目できるようになりますよ、という内容でした。

… 素晴らしい、そして課題が増えました。まだまだ遠いなー


それにしても、裏番も超強力で、全部聞けなかったのが非常に悔しい。特に聞き逃した感あったのが「金融系IT企業におけるスクラムへの挑戦(Yasumi Nakanoさん / Hiroko Shimaseさん)」。一体、金融系というどうしようもない企業文化で、どうやってそこに挑戦していったのか、ビデオでいいから見たい… 後から概要聞いてみたら、やっぱりすごかったらしいです。

Publickeyで記事になっていました。注目度高い!

運営の関係者の皆さん、スピーカー・参加者の皆さんありがとうございました。来年も1月ごろに開催される予定です。募集は10月ぐらいかな?まだ参加されていない方、是非参加してみて下さい。またお会いしましょう。

ILのキホン – 第五回Center CLR年末会

WP_20151226_14_13_25_Pro_LI「ILのキホン」と言う題目で、Center CLR年末会で登壇してきました。

Center CLRも第五回で、毎回参加の方と新しく参加の方で程よく盛り上がってうれしい感じです。ChalkTalkもそうですが、遠方からわざわざ参加して頂いた方もありがとうございます。来年も継続して行きたいと思います。

さて、今回のネタは結構前から決めていたものの、年末の忙しさの為に十分な時間が取れず、直前に方針を決めてバタバタしてしまいました。猛省…

と言う事だけではないんですが、ちょっとセッションの進行を変えてみました。


Intermediate Languageのキホン

ILの事については、第一回のChalkTalk CLRで「ChalkTalk CLR – 動的コード生成技術(式木・IL等)」と題してディスカッションを行ったのですが、今回は本会でその展開をしようと思ったのです。

briefingしかし、ただILを説明したのでは、実際にILを使う具体的なシチュエーションでも提起できない限り、右から左へ流れていってしまうだけだと考えて、ハンズオンのような形式で進行してみました。

Emitが可能なコードを一から書いていると、なかなか本題にたどり着けないため、GitHubに元ネタとなるコードを用意しておき、Emitコードから書き始めれられるようにしました。

/// <summary>
/// コードをILで動的に生成するヘルパークラスです。
/// </summary>
internal sealed class Emitter : IDisposable
{
	private readonly AssemblyBuilder assemblyBuilder_;
	private readonly ModuleBuilder moduleBuilder_;

	/// <summary>
	/// コンストラクタです。
	/// </summary>
	/// <param name="name">アセンブリ名</param>
	public Emitter(string name)
	{
		var assemblyName = new AssemblyName(name);
#if NET45
		assemblyBuilder_ = AssemblyBuilder.DefineDynamicAssembly(
			assemblyName,
			AssemblyBuilderAccess.RunAndSave);
#else
		assemblyBuilder_ = AssemblyBuilder.DefineDynamicAssembly(
			assemblyName,
			AssemblyBuilderAccess.Run);
#endif
		moduleBuilder_ = assemblyBuilder_.DefineDynamicModule(name + ".dll");
	}

	/// <summary>
	/// Disposeメソッドです。
	/// </summary>
	public void Dispose()
	{
#if NET45
		// デバッグ用に出力
		assemblyBuilder_.Save(moduleBuilder_.ScopeName);
#endif
	}

	/// <summary>
	/// 引数と戻り値を指定可能なメソッドを定義します。
	/// </summary>
	/// <typeparam name="TArgument">引数の型</typeparam>
	/// <typeparam name="TReturn">戻り値の型</typeparam>
	/// <param name="typeName">クラス名</param>
	/// <param name="methodName">メソッド名</param>
	/// <param name="emitter">Emitを実行するデリゲート</param>
	/// <returns>デリゲート</returns>
	public Func<TArgument, TReturn> EmitMethod<TArgument, TReturn>(
		string typeName,
		string methodName,
		Action<ILGenerator> emitter)
	{
		// クラス定義
		var typeAttribute = TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract;
		var typeBuilder = moduleBuilder_.DefineType(
			typeName,
			typeAttribute,
			typeof(object));

		// メソッド定義
		var methodAttribute = MethodAttributes.Public | MethodAttributes.Static;
		var methodBuilder = typeBuilder.DefineMethod(
			methodName,
			methodAttribute,
			typeof(TReturn),
			new [] { typeof(TArgument) });

		// ILGenerator
		var ilGenerator = methodBuilder.GetILGenerator();

		emitter(ilGenerator);

		// 定義を完了して型を得る
		var typeInfo = typeBuilder.CreateTypeInfo();
		var type = typeInfo.AsType();

		// メソッドを取得する
		var bindingFlags = BindingFlags.Public | BindingFlags.Static;
		var method = type.GetMethod(methodName, bindingFlags);

		// デリゲートを生成する
		return (Func<TArgument, TReturn>)method.CreateDelegate(
			typeof(Func<TArgument, TReturn>));
	}
}

public static class Program
{
	public static void Main(string[] args)
	{
		// 動的アセンブリを生成
		using (var emitter = new Emitter("TestAssembly"))
		{
			// メソッドを動的に生成
			var func = emitter.EmitMethod<int, string>(
				"TestNamespace.TestType",
				"TestMethod",
				ilGenerator =>
				{
					ilGenerator.Emit(OpCodes.Ldstr, "Hello IL coder!");
					ilGenerator.Emit(OpCodes.Ret);
				});

			// 実行
			var result = func(123);

			// 結果
			Console.WriteLine(result);
		}
	}
}

Emitterクラスは、System.Reflection.Emitの煩雑なコードを隠ぺいするために定義しています。課題の多くはMainメソッド内のラムダブロックのEmitメソッドをいじります。

# なお、このコードはnetcore5向けでも実行可能にしてあります。

課題はPart1~Part10まで用意し、はじめに少しだけ「スタックマシン」についての解説を行いました。


Part 1: 引数の値をToStringして返すようにする

part1GitHubのコードを軽く説明して、Emit本体のコードを課題に従って書き換えてもらいました。

テンプレートのコードは、”Hello IL coder!”と言う文字列を返すだけのものですが、引数で与えられたintの値を文字列に変換して返すのが課題です。

最初なので、こんなものだろうと思っていたのですが… 実はこれ、大変な罠が…

やるべきことは、スライド通り大きく3つあります。

引数の値はどうやって入手するか

ヒントに書いておいたのですが、「OpCodesクラス」を見ると、IL命令の一覧が確認できます。

そして、スタックに値を積む(プッシュ)するのは、「Ld~」で始まる命令であることも説明しました。OpCodesの一覧を見ていると、「Ldarg_0」命令が見つかります。これを使うと、引数の最初の値をスタックに積みます。「Ldarg」や「Ldarg_S」命令でもOKです。その場合は、Emitメソッドの引数にインデックス(0ですが)を指定します。

Int32.ToStringのMethodInfoはどうやって入手するか / staticメソッドの呼び出し

メソッドを呼び出すには、「Call」命令を使います。その時、Emitする引数に、呼び出すメソッドの「メソッド情報(MethodInfo)」が必要です。これには、リフレクションを使います。

// Int32のTypeクラスのインスタンスを得る
Type int32Type = typeof(System.Int32);

// Int32.ToStringメソッドのメソッド情報を得る
MethodInfo toStringMethod = int32Type.GetMethod("ToString", Type.EmptyTypes);

ToStringのオーバーロードは複数あるので、正しいメソッド情報を選択するために、Type.EmptyTypesフィールドを使って、「引数0」のオーバーロードを選択させています(このフィールドを使わなくても、0個の配列でもOKです)。

typeofを使うことに対して、一部の方がモヤモヤしていた(チートっぽい)ようです。厳密に0からInt32のTypeを取得したことにならないのではないかとの事で、確かにその通り。typeofを使うと、C#コンパイラがコンパイル時にInt32を解決しようとします。実行時に0から取得する事も不可能ではないのですが、その話をするとまた話がずれていくので、今は諦めてもらう事に。

# Call命令を使うかどうかの更なる議論がありますが、それは後のPartで…


「動かない!」

複数のチームから「動かない!」とか、「NullReferenceExceptionがスローされる!」とか騒ぎが…

ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Call, toStringMethod);
ilGenerator.Emit(OpCodes.Ret);

どう見ても間違っていないように見える… そこで、「ILSpy」を使って、C#で書いた等価コードを逆アセンブルした所、引数をスタックに積む際に、「Ldarga」命令を使っていました。

やられた orz

Ldarga命令は、対象の値そのものではなく、その値を示す参照(ポインタ)をスタックに積みます。これは、対象の値が「値型(ValueType)」であり、Callでそのメソッドに遷移した先ではthis参照として扱われなければならないため、Ldarg命令ではなく、Ldarga命令を使う必要があったのです。

Int32の場合は値型と言っても、なぜ参照化しなければならないのか、ピンと来ないかもしれません。以下に構造体(構造体も値型)を使った例を示します:

// 構造体
public struct Hoge
{
	public int A;
	public int B;
	public int C;

	// 計算し、結果をCに代入する
	public void Compute()
	{
		// 計算結果を代入するには、this(自分自身への参照)が必要
		this.C = this.A + this.B;
	}
}

// メソッドを呼び出して計算させる
Hoge hoge = new Hoge();
hoge.A = 1;
hoge.B = 2;
hoge.C = 123;
hoge.Compute();

// hoge.Cは、123か3か?
Console.WriteLine(hoge.C);

Computeメソッドはインスタンスメソッドなので、内部でthis参照が使えます。this参照を使った場合、そのインスタンスは、呼び出し元のhogeとなるはずです。Computeメソッドから呼び出し元が保持するインスタンスにアクセスさせるには、文字通り「参照」が必要なのです。従って、ここではLdarga命令を使って、インスタンスへの参照を渡す必要があります。勿論、対象が参照型(クラス)であれば、Ldarg命令で問題ありません。

# NullReferenceExceptionがスローされたのは、本当に偶然のようで、真の原因は分かりません。


全然足りない!!

そんなわけで、このPart1を30分ぐらいでやるつもりだったのが、これだけで時間使い切ってしまいました。値型と参照型の違いは、実際にはPart6ぐらいでやってもらう予定だったのが、こんな所でやる事になったのが敗因…

時間を延長して続けるかどうかという話もあったのですが、他セッションもあったため、次回に持ち越しとなりました。次回に同じ題目で再チャレンジします。ごめんなさい。スライドはその時まで正式公開はしません。次回も同様に参加する場合は、Part1の内容の復習をしておいてくだしあ。


詰め込みすぎは良くない

年末は… ダメですね。この記事も31日に書いてるし (;´Д`)
でも楽しかったです。来年もよろしくお願いします!

ChalkTalk CLR – COMのすべて

WP_20151219_11_31_11_Pro_LI本日は「ChalkTalk CLR – COMのすべて」と題して、COM(Component Object Model)についてのディスカッションを行ってきました。

参加された方はCOM方面に強い方が半数近くいて(MVP4人、元MVP2人、中には現役で開発やってるという人も)、とにかくこれ以上ないぐらい強力なメンバーで、濃い議論が交わされました。多分、もうこういう企画は無いかな感全開 (;´Д`) 遠方からの参加もありがとうございます。 ChalkTalkバンジャーイ

この記事では、内容を「要約」して記載します。図については内容を考えて起こしなおしました。

前回と同じく、Maker Lab NAGOYAさんのスペースをお借りしました。ありがとうございました。


事前の洗い出し

WP_20151219_18_18_54_Proまず、各参加者のCOMに対してのスキルセットを「バリバリ解説OK」~「COMって何」のレベルで、かつ自分がCOMに対して抱えている課題、という切り口で、いつも通り付箋に5分で書いてもらって張り出しました。

写真は横に集約した後の状態なので分かりにくいですが、上から「COMって何」~「バリバリ解説OK」の順で並べてあります。要約すると:

  • COMの概念的なものへの疑問
  • COMとWinRTとの関係
  • COMの将来展望
  • COMの実践的な実装方法
  • スレッドアパートメントとは何か
  • マーシャリングとは何か

という内容が全体的に知りたいとことと認識できたので、順に掘り下げていきました。


COMの概念的なものへの疑問

そもそもCOMとは何で、何のための技術?技法?なのか? という基本的な疑問です。ディスカッションでは、Essential COM第一章や、Inside OLEでのIUnknownインターフェイスの実装例の話から:

  • 処理系の問題1: リンカー(C言語の)のリンケージの問題
  • 処理系の問題2: メソッドの呼び出し規約の問題
  • コンポーネントインスタンスの生存期間の問題
  • インターフェイス型の認識の問題

と言う問題の解決に集約されるという意見交換がなされました。

処理系の問題

処理系の問題とは、C/C++コンパイラ・リンカーが、それぞれの製品(例:VC++、gcc、BC++など)で、シンボル名の内部的な命名規約が独自仕様である事と、メソッドのエントリポイントが特定されたとしても、その呼び出し規約が異なる事がある、と言う事です。

処理系によってシンボル名が異なる(特にC++において)問題は、シンボル名に引数の型情報を含ませることから発生します。このシンボル名の操作の事を「マングリング(Mangling)」と呼び、その仕様は処理系によって異なります。また、同じ処理系でもバージョンによっても異なる可能性があります。

dependencywalker1(.NETではなく)Win32レベルでのDLLのエクスポートシンボルを調べるツールとして「Dependency Walker」と言うツールがあります。これを使ってEXEやDLLを調べると、エクスポートシンボルの名前が分かります。

このスクリーンショットでは「KERNEL32.DLL」のエクスポートシンボルを見ています。赤枠に表示されていますが、普通にWin32 APIのシンボル名が見えます。シンボル名が普通に見える関数は、呼び出し規約が「stdcall」である事を示しています。

dependencywalker2対比する形で、「MSVCRT.DLL」を見てみます。赤枠を見ると、「exception」とか「badcast」とか読み取れる単語もありますが、何やらわけのわからない記号が沢山見えます。これがマングリングされた状態のシンボル名で、VC++のシンボル名規約に従って変換された結果です。

dependencywalker3そういえば、実際のところこれがC++のどんなシンボルに相当するのかを実演しなかったのを思い出しました。これが、デマングリングした結果です(メニューから「Undecorate C++ symbol」を選択すると表示されます。見比べてみると面白いと思います)。

このような、C言語で言うところの関数名だけでは表現できない、C++のオーバーロードやクラスメンバ関数、const関数などを、シンボル名だけで識別可能にするために、マングリング操作が行われます。そしてこのマングリング操作が、処理系依存(かつバージョン依存)なのです。

したがって、例えばVC++ 6.0で普通に作ったDLLとVBで作ったDLLを直接静的にリンクすることは出来ません。他の処理系についても同様です。COMは、これらの処理系依存シンボルを使わず、COMのランタイムが提供する情報だけでクラスのファクトリを特定し、メンバ関数の位置はvtableと呼ばれる関数ポインタのテーブルの並びの整合性を担保することで、この問題を回避します。

# vtableの話はディスカッションでやらなかったですね。vtableはC++のvtableそのままで、仮想関数へのポインタのテーブルです。

どうやってクラスを特定するのかは、レジストリのHKEY_CLASSES_ROOT配下の情報を使います。ATLでプロジェクトを新規に生成すると、プロジェクト内に*.rgsのようなファイルが配置されます。これはコンポーネントレジストラスクリプトと呼び、要するにregsvr32された時にレジストリのどこにどんな値を書き込むのかを表し、このスクリプトでCOMのクラス(coclass)が特定できるようにします。

# 特定には、COMコンポーネントの実装が、どこのパスにあるDLL内にあり、どのクラス(CLSID –> GUID)なのかという情報を含みます。

もう一つの問題がメソッドの呼び出し規約で、これはWikipediaにあるように複数の規約があり、VC++では各関数単位でこれを指定して実装できるのですが、COMでは例外なく「stdcall」を使います。

生存期間

生存期間とは、COMのクラスのインスタンスが、いつまで生き続けるのかと言う問題です。.NETのインスタンスは、誰からも参照されなくなり、GCが回収する(場合によってはファイナライザーが呼び出され)と死んだ事になるのですが、C言語のようなアンマネージな世界では、「malloc」や「new」によってインスタンスが(ヒープに)生成され、「free」や「delete」によってメモリから取り除かれます。

// 確保する
auto p = static_cast<int*>(malloc(123 * sizeof(int)));

// 使う...

// 解放する
free(p);

このような簡単な例であれば、確保して解放するというサイクルから、解放のタイミングの時点で正しく解放されることが分かります。しかし:

static const int* pUseLate = nullptr;

static void ComputeStep1(const int* pTarget)
{
	// 後で使いたいので保存
	pUseLate = pTarget;
}

static void ComputeStep2()
{
	// (pUseLateを使って何かやる)
}

// 確保する
auto p = static_cast<int*>(malloc(123 * sizeof(int)));

// 使う...

// 処理その1
ComputeStep1(p);

// 処理が終わったので解放する
free(p);

// 処理その2
ComputeStep2();

このような処理があった場合、ComputeStep2の内部実装では、既にpが解放されていることを知る由はありません。このサンプルコードは非常に短く、メインの処理とComputeStepの実装が並べて書いてあるので「こんなのダメに決まってるじゃん!」と分かりますが、ComputeStepの実装は「別のだれか」がやるとしたらどうでしょうか? あるいは、既にComputeStepは別の誰かが実装済みであり、これからメインの処理を書く場合、ComputeStep2の実行前にpを解放してはならない事はどうやって分かるのでしょうか?

このような、インスタンスの生存期間の問題を解決する方法として、COMではクラス実装側に参照カウンタを持ち、このカウンタ値を監視することで不要になったかどうかを判定します。

生存期間のカウンタは、IUnknownインターフェイスAddRefによってカウントアップされ、Releaseによってカウントダウンされます。カウンタの初期値は1で、AddRefのたびにインクリメントされ、Releaseが呼び出されるとデクリメントされます。カウンタが0になると、自己消滅するように実装します。

使用する側は、ポインタをコピーするときにはAddRef、使い終わったらReleaseするという規約を守る事により、生存期間が正しく管理されるようになります。

インターフェイス型の認識

インターフェイス型の認識の問題もあります。C++上でインターフェイスを示すポインタをキャストして、目的のインターフェイスへのポインタを取得しようとしたとします。

// CoCreateInstanceなどで取得したインターフェイスポインタ
IUnknown* pUnknown = ...;

// 目的のインターフェイスポインタを得るためにキャスト
ICalc* pCalc = static_cast<ICalc*>(pUnknown);

このキャストが正しく解釈されるかどうかは、処理系に依存します。IUnknownをICalcにするにはダウンキャストが必要ですが、C++の世界ではコンパイラがその判断を行えるものの、COMの世界ではインターフェイスの情報しか存在しないため、正しくキャストできる保証がありません。そのため、キャストと言う処理自体も、IUnknown.QueryInterfaceメソッド呼び出しで解決します。

// CoCreateInstanceなどで取得したインターフェイスポインタ
IUnknown* pUnknown = ...;

// 目的のインターフェイスポインタを得るためにキャスト
ICalc* pCalc = nullptr;
pUnknown->QueryInterface(IID_ICalc, reinterpret_cast<void**>(&pCalc));

第一引数はインターフェイスID(GUID)で、これが必要なのはCOMの世界は.NETとは異なりリフレクションが存在しないので、インターフェイス型を一意に識別するIDが必要だからです。勿論、この場合のIID_ICalcは、ICalcインターフェイスのIDである前提です。

QueryInterfaceメソッド内でキャストを実装することにより、処理系の範囲内で正しくキャストが行えます。返されるポインタはキャスト後のポインタであるため、呼び出し元は処理系に依存することなくキャスト出来たことになります。

このようなメソッドを提供することで、処理系に(C++固有の)型を認識させる必要をなくし、互換性を維持します。

COMの概念への展開

結局、IUnknownの実装を通じて、どうやって処理系に依存しないでバイナリ互換性を維持するのかという事が、COMの中心にある概念であることを確認しました。COMのIUnknownとその実装はこんな感じだ!というのを、C#ライブコーディングで疑似実装して、参照カウンタの動きや、QueryInterfaceによるキャストの挙動を示して、内容の共有をしました。

# 以下のコードは、クラスファクトリも含めて穴埋めしたコードです。

// (あくまで概念コードです。これは正式なCOMのコードではありません)
public interface IUnknown
{
	T QueryInterface<T>() where T : IUnknown;
	int AddRef();
	int Release();
}

public interface ICalc : IUnknown
{
	int Add(int a, int b);
}

// クラスは直接公開されない
internal sealed class CalcImpl : IUnknown, ICalc
{
	private int count_ = 1;

	public int AddRef()
	{
		return Interlocked.Increment(ref count_);
	}

	public int Release()
	{
		var current = Interlocked.Decrement(ref count_);
		if (count == 0)
		{
			// インスタンスの破棄処理
			// Disposeに近いが、メモリストレージも削除される。
			// 破棄処理を隠蔽することで、処理系依存を排除。
		}
	}

	public T QueryInterface<T>() where T : IUnknown
	{
		// CやC++にはリフレクションは無いので、本当はインターフェイスID(GUID)で判定する。
		// キャスト処理を隠蔽することで、処理系依存を排除。
		if (typeof(T) == typeof(ICalc))
		{
			Interlocked.Increment(ref count_);
			return (T)(object)this;
		}
		if (typeof(T) == typeof(IUnknown))
		{
			Interlocked.Increment(ref count_);
			return (T)(object)this;
		}

		throw new NotImplementedException();
	}

	public int Add(int a, int b)
	{
		return a + b;
	}
}

public interface IClassFactory
{
	T CreateInstance<T>(Guid clsid) where T : IUnknown;
}

// クラスファクトリクラスも公開されない
internal sealed class CalcClassFactory : IClassFactory
{
	public T CreateInstance<T>(Guid clsid) where T : IUnknown
	{
		if (clsid == CLSID_Calc)
		{
			// newの実装を隠蔽することで、処理系依存を排除。
			return new CalcImpl();
		}

		throw new NotImplementedException();
	}
}

// CalcImplを外部から特定するためのGuid
public static readonly Guid CLSID_Calc = new Guid("BD4D1DDD-9C28-4432-A8DD-9CFA77E6433F");

// DLL外からはこのエントリポイントだけが見える
public static IClassFactory DllGetClassObject()
{
	return new CalcClassFactory();
}

もう既にかなりアレですが、まだまだ続きます。


COMとWinRT

discussion_allaboutcomWinRT(ストアアプリ・UWPのフレームワークとして使われるライブラリ)は、基礎がCOMで出来ていることは分かっていましたが、それ以上の理解は私の中になく、あんまり調べる意欲もなかったので、知りたかった部分です。

全てのCOMクラスはIUnknownインターフェイスを実装しますが、WinRTのクラスは、IInspectableインターフェイスも実装します。COMの世界ではITypeInfoインターフェイスをタイプライブラリ情報を公開するのに使いますが、これのWinRT版のような位置づけです。WinRTでは型情報は.NETのメタデータをそのまま使用します(winmd)。この情報との対応付けにIInspectableインターフェイスを使います。したがって、ITypeInfoのように、情報の細部までトラバース可能なメソッドセットは持っていません。

なお、COMにおいてITypeInfoインターフェイスの実装は任意ですが、WinRTのIInspectableの実装は必須です。

WinRTにおいてのもう一つの事情は、スレッドアパートメントの扱いです。WinRTでは、メインスレッドに相当するスレッドがASTAに属し、それ以外のスレッドはMTAに属するそうです。各アパートメント間の関係は、通常のCOMと同一とのこと。なるほど。

# 余談ですが、MSDNライブラリのリンク壊れてたりするよねー、直してほしいよねーみたいな話が… TechNetもぶっ壊れてるなー |д゚) チラッ

こんな話をすると、当然「アパートメントとは何か」みたいなところを避けては通れないけど、とりあえずは次の課題へ。


COMの実践的な実装方法

ようするに、どうやってCOMを書けば良いのかわからない(.NETとかVB6以外で)。

ATLでやる

ということで、一からIUnknownをCで実装するというハードは方法は時間もないのでパスし、ATL(Active Template Library)での実装方法をライブデモしました。ライブデモは私がやったのですが、ATLでバリバリ書いていたのはVisual Studio 2005の時だったので、2005を使ってデモ(こんなこともあろうかと、2005はインスコしてあるのだよ)。ATLは途中から、C#のような属性ベースの指定で楽に書けるようになったはずなのですが、結局私的には移行しなかったので分からず…

atlwizard新しいプロジェクトの追加で、「ATLプロジェクト」を追加し、「プロジェクト-追加-クラス」で、「ATLシンプルオブジェクト」を選択すると、COMをATLで実装するクラスのひな型が追加されるウィザードが表示されます。短い名前の所にクラス名を入れると、各ファイル名とか良しなに決定されます。問題は次のウィザード。

ここのスレッドモデルとアグリゲーションの選択も、結構ディープだよね、ああそれとフリースレッドマーシャラーとかも。でもこの説明をするには、やっぱりアパートメントを避けては通れないしデモが中断してしまうので、ちょっと横に置いておく事に。

これでひな型が生成されたら、あとは「クラスビュー」のCOMインターフェイスのツリーから「追加-メソッド」とか「追加-プロパティ」とかやると、メンバを追加するウィザードが表示されて、メンバ定義を簡単に追加できます。もし、手動で定義するとなると、IDLファイル・ヘッダファイル・ソースファイルを同時に修正しなければならないため、かなり面倒です。そして、追加はウィザードで簡単にできますが、修正するとなると同じように全部直さなければならないので、結構苦痛…

で、実装出来たらビルドしますが、Visual Studioが管理者権限で起動していないと、最後にエラーが出ます。これはregsvr32でDLLを登録しようとしたときに、コンポーネントレジストラスクリプトがレジストリを変更するためで、素直に再起動。

TestPropという文字列(BSTR)のプロパティを追加し:

// メンバには以下のようなフィールドを定義
private: _bstr_t temp;

STDMETHODIMP CTest::get_TestProp(BSTR* pVal)
{
	// TODO: ここに実装コードを追加してください。

	*pVal = temp.copy();
	return S_OK;
}

STDMETHODIMP CTest::put_TestProp(BSTR newVal)
{
	// TODO: ここに実装コードを追加してください。

	temp = newVal;
	return S_OK;
}

テストするコードはC#で書きました。
COM側のビルドとregsvr32が完了していれば、C#の参照設定でCOMコンポーネントが参照出来ます。

using ATLSampleLib;

class Program
{
	static void Main(string[] args)
	{
		var testClass = new TestClass();
		testClass.TestProp = "ABCX";
		Console.WriteLine(testClass.TestProp);
	}
}

これで無事、コンソールに”ABCX”が表示されました。

WinRTでは文字列をHSTRING型で扱いますが、これはリテラル文字列をいちいちメモリ確保してコピーして…というコストを削減するためだそうです。なるほど、それだとCLRの世界での文字列と同じように扱われる訳で、イメージは湧きやすい。BSTRを使うと、内部ではSysAllocString APIなどでメモリを確保したり解放したりするので、コストは高いです。

いつ解放されるのか

上記テストコードで、AddRefもReleaseも呼び出していないのは?という疑問に対し、CLRの世界にはRCW(ランタイム呼び出し可能ラッパー)があって、これがAddRefとかReleaseを呼び出しているのだ、しかし、この例では使用後にすぐプロセスが終了してしまうので、果たして呼び出されるのか?という疑問があり、じゃあ、Marshal.FinalReleaseComObjectを呼び出せばどうだ、いや、それを呼び出すと挙動がおかしい、などの説があって、試しました。

using ATLSampleLib;

class Program
{
	static void Main(string[] args)
	{
		var testClass = new TestClass();
		testClass.TestProp = "ABCX";
		Console.WriteLine(testClass.TestProp);

		Marshal.FinalReleaseComObject(testClass);
	}
}

ATL側では、インスタンスの寿命が尽きる時(参照カウンタが0になったとき)、FinalReleaseメソッドが呼び出されます。ここにブレークを張ってテストしたのですが、やってこない…

FinalReleaseComObjectの代わりにGCを手動実行したらどうだろうと言う事でテスト:

using ATLSampleLib;

class Program
{
	static void Main(string[] args)
	{
		var testClass = new TestClass();
		testClass.TestProp = "ABCX";
		Console.WriteLine(testClass.TestProp);

		GC.Collect(2, GCCollectionMode.Forced, true);
		GC.Collect(2, GCCollectionMode.Forced, true);
		GC.Collect(2, GCCollectionMode.Forced, true);
	}
}

すると来ましたFinalRelease。うーむ、FinalReleaseComObjectとは何なのか? これだとインターフェイスポインタを直接弄った方が安心感があるよなぁと思わなくもない(真相は分からず)。

finalreleasestacktrace1もう一つ気が付いたことが。FinalReleaseでブレークを張ったところ、スタックトレースがこのように。

これワーカースレッドから呼ばれてるよね、何でだ?と思ったらMainメソッドに、STAThread属性を付けていなかった。

finalreleasestacktrace2で、STAThreadを追加したところ、無事にメインスレッドから呼び出された(このCOMコンポーネントがワーカースレッドから呼び出されること自体に問題はないんですが、一瞬えっと思ったので気が付いた次第)。

# しかし、このスタックトレースも大概だなぁ。当然こうなる事は予測できるんだけど…

さて、これの何が問題かと言う事は、やっぱりアパートメントの話に踏み込まないと説明できないよね。と言う事で、そろそろ参加者の意識共有も出来たので、アパートメントを話をし始めました。


アパートメントとは何なのか?

apartment_diagramアパートメントとは、スレッドとCOMのインスタンスをグループ分けする概念で、そのグループとは何なの?というディスカッションをしました。グループの種類は「STA(シングルスレッドアパートメント)」と「MTA(マルチスレッドアパートメント)」の二種類(あと、概念的にはメインSTA)があります。

アパートメントの概念が難しいのは、用語の説明から入らざるを得ないからかなーと思います。このような図が一般的に説明に用いられますが、内部実装的にこうなっているわけではなく、あくまで概念的に分割される、という事も、難解なところかなと。混乱すると良くないので、内部もこの通りになっていると仮定してもいいんじゃないかと思います(実際は違うって事が分かるようになったら、もう説明も不要になってるはず)。

シングルスレッドアパートメント

一つのスレッドだけが特定のSTAに属します。新たなスレッドを作り、STAに属させると、図の通り新しいSTAが作られます。メインスレッドがSTAとなった場合は、特別に「メインSTA」と呼びます(実際には、プロセス内で最初にSTAとして初期化されたスレッドです)。スレッドをSTAに属させるには、CoInitializeEx APIを実行します。.NETの場合は、メインスレッドであれば、Mainメソッドに「STAThread属性」を適用し、ワーカースレッドを作る場合は、Thread.SetApartmentStateメソッドで指定します。

図のように、STAは他のアパートメントと(概念的に)分離されており、そこに属するスレッドやインスタンスは、別のアパートメントから直接参照することが出来ないという原則に従います。

マルチスレッドアパートメント

MTAは、プロセス内にただ一つだけ存在します。そして、MTAに属するスレッドは複数存在できます。スレッドをMTAに属させる方法も、STAとほぼ同等です。Mainメソッドに適用する場合は、「MTAThread属性」を使用します。

STAとアパートメント間の特性

apartment_diagram2STAとMTAの最大の違いは、STAは「ウインドウメッセージキュー」に依存していると言う事です。今までのCOMの説明にはウインドウとかコントロールのようなユーザーインターフェイスの話は全く出て来なかったので、「えっ」てなってました。

ここで説明の順序を逆にして、遠い昔、Win16(つまりWindows 3.1以下)の頃は、マルチスレッド対応がそもそも無く(_beginthreadex APIとか存在しない)、すべての操作を非同期的に処理するためには、メッセージポンプにメッセージを配信する形で協調的に動作させる(ノンプリエンティブマルチタスク)必要がありました。

この頃に作られたアプリケーションは、マルチスレッドのように横から突然割り込まれてメソッドが実行されるような事を全く想定していません(スレッドがそもそも存在しないので)。今でいうところのモニターロックや排他制御がまったく不要だったのです。そのため、マルチスレッド環境で別のスレッドからこのような実装を呼び出すと、容易に破たんします。そして、現在のユーザーインターフェイスも全く同様の構造なのです。

つまり、異なるスレッドからウインドウ要素を操作したりする場合は、すべからく安全策をとる必要があります。WPFであればDispatcherを使う、Windows FormsであればInvokeメソッドを使って簡単に呼び出しの「マーシャリング」を実行できます。しかし、COMが考案されたころには、そのような強力な道具立てがなかったのです。

更に、既存のマルチスレッド非対応コードがバイナリライブラリとして「変更不能」な状態で存在するため、互換性も担保する必要があります。この問題をフレームワーク(COMランタイムライブラリ)で隠蔽し、どうにかして安全性と実装の軽減を両立させたかったのだと推測されます。

apartment_diagram5STAがウインドウメッセージキューに依存しているとは、STA内のCOMコンポーネントインスタンスを操作する場合は、このキューに一旦要求(メソッド呼び出し情報)を格納し、「協調的」に処理させると言う事です。

勿論、STA内のスレッドであれば、同じSTA内のインスタンスは、キューを介することなく操作できます。勿論、メソッド呼び出し結果の返却も、キューに一旦結果を格納して受け取ります。このときのキューは、呼び出し側のキューを使います。

このように、ウインドウメッセージキューを介在させることで、ウインドウメッセージ(ウインドウの移動やリサイズ・ボタンのクリック・テキストの変更など)にいきなり割り込むことなく、順番に規則的に実行されるので、マルチスレッド競合について考えなくても良くなる、というのが、STAに属する利点となり、同時に高いオーバーヘッドが欠点となります。

apartment_diagram6ところで、STAとMTA間で呼び出しを処理する場合は、呼び出し側がMTAであれば、呼び出し時はSTAのキューを使いますが、処理結果はキューに入らず、直接元のスレッドに伝達されます。逆であれば、呼び出しがキューに入らず、結果は一旦キューに入ります。

これは、MTAは特定のウインドウメッセージキューに紐づく事がないからです。したがって、ユーザーインターフェイスを含むCOMコンポーネントをMTAに属させると、クラッシュするなどの予期しない結果を引き起こします。

そして、MTAには複数のスレッドが同居出来て、MTA内のCOMインスタンスはすべて介在される事なく直接呼び出しが成立するので、ほぼコスト0で高速にメソッド呼び出しが行われます。これが、IISにホストされるCOMコンポーネントをSTAではなくMTAで動作させなければならない理由です(MTAにするとパフォーマンスが向上するらしい、という理由で、背景を考えずにMTAに属させるコードを書くと、間違いなくハマります)。

COMインスタンスはどこのアパートメントに配置されるか

atlwizardあるアパートメントに属しているスレッドがCOMコンポーネントのインスタンスを生成(CoCreateInstance API)すると、コンポーネントに指定された属性に従って、インスタンスが配置されるアパートメントが変わります。再びATL COMオブジェクトウィザードのスクリーンショットですが、ここで、「スレッドモデル」を選択可能です。

このスレッドモデルの選択と、現在のスレッドがどのアパートメントに属しているのかによって、配置されるアパートメントが決定されます。昔、この組み合わせの表をどこかで見た記憶があるのですが、改めて書き出してみました(ただし、すべて検証しているわけではないので、間違っているかもしれません)。

メインSTA STA MTA
シングル メインSTA メインSTA メインSTA
アパートメント メインSTA STA STA
両方(Both) メインSTA STA MTA
フリー(Free) MTA MTA MTA
ニュートラル メインSTA STA MTA

この表を眺めていると、何を選択すべきかが見えてきます。

  • ユーザーインターフェイス(ウインドウメッセージを必要とする)を含むコンポーネントであれば、「アパートメント」を選択する。これには、俗に言う「ActiveXコントロール」も含まれます。
  • その中でも特にメインスレッド(メインSTA)でのみ動作可能なコンポーネントであれば、「シングル」を選択する。
    (今やこのようなコードは想像しにくいかもしれません。例えばグローバル変数に状態を持っていて、これを操作しているようなUIコンポーネントは、多分メインSTAでしか動作しません)
  • アパートメントの影響を受けないコンポーネントであれば、「両方」又は「ニュートラル」を選択する。
  • MTAにのみ必ず属させ、ハイパフォーマンスでローコストな環境を必要とするコンポーネントであれば、「フリー」を選択する。

再度確認したのが、MTAに属させたからと言ってハイパフォーマンスになるわけではなく、マーシャリングコストを最小化可能であることがミソなので、ここで不用意に「フリー」を選択すると、アパートメントの選択同様ドツボにはまる事に注意が必要です。

一番無難な選択肢が「両方(Both)」ですが、表の上では「ニュートラル」も同じとなっています。両方とニュートラルの違いは、両方とすると、メインSTA・STA・MTAのどこにでも配置出来るものの、配置されるとそのアパートメントに完全に紐づきます。対して、ニュートラルとは、本当のところは「どのアパートメントにも属さない」状態となります。この違いは、マーシャリングの動作に影響します。


マーシャリングとは

アパートメントの話は抽象的なので、もう少し実装に近寄ります。

apartment_diagram7アパートメントの境界を超えるために、COMのランタイムによって「プロキシ・スタブ」という一種のファサードが自動的に介在し、メソッド呼び出しを自動的にメッセージキューに保存し、目的のSTAのスレッドでこれを取り出して実行する、という処理を行う事を説明しました。一般的にこのような操作を行う事を「マーシャリング」と言います。

マーシャリングは、カスタム「プロキシ・スタブ」コードで完全に独自の実装を行うか、あるいは「COMスタンダードマーシャラー」を使うかを選択できます。正直カスタムマーシャラーは深くて闇で資料も少なく、これを自前で実装する事は殆ど無いと思います。

どちらにしてもこの図のように、アパートメント境界の間に入って、メッセージキューの操作をしたり、スタックを操ってリモートメソッド呼び出しを成立させる役割を担います。呼び出し元は、カスタムマーシャラーが「何故か」目的のインターフェイスを実装したインスタンスに見えるので、アパートメント内呼び出しと全く同じように、インターフェイスメソッドを呼び出す事が出来ます。

# IUnknown.QueryInterfaceメソッドは、インターフェイスIDを受け取って、そのインターフェイスへのポインタを返します。と言う事は、COMスタンダードマーシャラーが、実際には実装を用意していなかったとしても、IID_ICalcのようなインターフェイスIDが存在して、キャストも成立させたように見せかければ、この状況を作り出す事ができます。返されるインターフェイスポインタは、ICalcを動的に実装したスタブクラスです。

さて、このマーシャラーが一体いつ挿入されるのかが、背景を知っていないと分からないと思うため、その説明をしました。

インスタンス生成時

apartment_diagram8CoCreateInstance APIでインスタンスを生成するとき、呼び出し元のスレッドが属するアパートメントと、COMコンポーネントに指定された「スレッドモデル」に応じて、インスタンスが配置されるアパートメントが決定されます。では、呼び出し元スレッドのアパートメントと異なるアパートメントにインスタンスが生成され、配置された場合、呼び出し元はどうやってそのインスタンスにアクセス出来るのか?

答えは、CoCreateInstanceが返すインターフェイスポインタが、「既にCOMスタンダードマーシャラーなどのファサードを示している」ので、呼び出し元は何も考えなくても良い、と言う事です。

この図のように、MTAのワーカースレッドは、CoCreateInstanceが返すインターフェイスポインタを、本物の「ICalc」を実装したインスタンスだと思い込んでいます。しかしその実態はマーシャラーであり、ICalcのメソッドを呼び出すとマーシャラーのメソッドを呼び出し、後は前述のとおり、マーシャリング動作が行われます。

インターフェイスポインタの自動伝搬時

内部的にはここが難しい所で、一度マーシャラー経由のインターフェイスポインタを取得した後は、メソッド呼び出し引数や戻り値にインターフェイスポインタが含まれていれば、マーシャラーが自動的にそのインターフェイスポインタにもマーシャラーを挿入します。

// 呼び出し元の例:
IHogeService* pHogeService = ...;

// 例えば、戻り値がインターフェイスポインタの場合
IResultData* pResultData = nullptr;
pHogeService->GetResult(&pResultData);

// (pResultDataは何を指しているか?)

apartment_diagram9GetResultメソッドの戻り値がIResultDataという、別のCOMコンポーネントインスタンスへのポインタである場合、その実態は呼び出し先のアパートメントに存在する可能性が高いです(必ずと言うわけではない)。

その場合、戻り値として返されるインターフェイスポインタは、あくまで呼び出し先のアパートメントのインスタンスを示しているので、それがそのまま呼び出し元に返されても、その後が困ります(そのまま使うとマーシャリングが行われないので、アパート境界が守られず、酷い問題が発生する)。

そのため、スタンダードマーシャラーは、戻り値のインターフェイスポインタを「マーシャリング」し、新たなマーシャラーを割り当て、マーシャラーのインスタンスへのポインタを呼び出し元に返却します。

呼び出し元がIResultDataのメソッドを呼び出すと、裏ではマーシャラーを介してインスタンスを操作することになり、安全性が担保されます。呼び出し元はIResultDataがマーシャラーである事は知る由もなく、直接呼び出しているのと変わりません。

さて、これを実現するには、引数や戻り値に「インターフェイスポインタが含まれているかどうか」を認識出来なければなりません。CやC++のソースコード上では、インターフェイス型を指定するので(人間の目には)判別できます。しかし、コンパイル後のバイナリには、型情報が含まれないため、自動的に検出する事が出来ません。COMスタンダードマーシャラーが、ノーメンテで動作するには、型の自動判別が可能である必要があるのです。

方法は2つあります。一つは、インターフェイス型のメタデータをタイプライブラリ(*.tlb)として読み取り可能になるようにファイルを配置するか、リソースとしてDLLに埋め込み、「プロキシ/スタブデータ」を提供することです。これにより、COMスタンダードマーシャラーは、引数や戻り値の型情報にアクセスし、それがマーシャリングの対象である事を認識できます。

もう一つは、同じくタイプライブラリを提供する必要がありますが、引数や戻り値の型にVARIANT型を使う事です(プロキシ/スタブデータは不要)。VARIANT型は、格納できるサブタイプを指定して値を格納します。サブタイプの種類にハードコーディングされた制限がありますが、基本的なプリミティブ型やインターフェイスポインタなどを含むことが出来、何が含まれているのかを検知出来ます。但し、VARIANT型を使うと、タイプセーフ性は低下します。

# もちろん、カスタムマーシャラーを書いた場合は、自分でマーシャリングを行うため、タイプライブラリの準備やVARIANT型への依存は不要です。また、IDispatchインターフェイス経由でのみ公開されるメソッドの場合は、すべてVARIANT型である事を想定可能なので、タイプライブラリは不要です。

インターフェイスポインタの手動伝搬時

自動マーシャラーを全く介さないというソリューションもあります。一つは「フリースレッドマーシャラー(FTM)」を集約する事です。詳細は省きますが、この場合はマーシャリングを自分で面倒見なければなりません。もう一つが、スレッドモデルを「ニュートラル」とした場合です。この場合もマーシャリングは自分で面倒を見る必要があります。

マーシャラーを全く介さないと、メソッド呼び出しはダイレクトにCOMインスタンスのメソッドを呼び出す事になります。当然、引数や戻り値に渡されるインターフェイスポインタも生ポインタであるので、アパートメントの境界を越えてアクセスして良いかどうかは全く分かりません。

そのような場合に自分でマーシャリングを実行する必要がありますが、2つの方法があります。

APIやGITを使うと、インターフェイスポインタを再取得したときに、マーシャリングが必要だと判断されれば、マーシャラーが生成されて、マーシャラーへのポインタが返されます。不要と判断されると、直接COMコンポーネントインスタンスへのポインタが返されます。

# 回り回って元のアパートメントでインターフェイスポインタを取得すると、ちゃんと再計算されて、生ポインタが取得できた、ハズ…

現在ではGITを使う方が簡単です。特にATLを使う場合は、CComGITPtrクラスを使うと、非常に簡単にマーシャリングを行うことが出来ます。GITで言うところの「Cookieデータ」が、APIを使う場合の抽象的なデータに相当します。

何故、FTMやニュートラルというオプションがあるのかと言うと、お互いのCOMコンポーネントが何であるかが確実で、お互いに共謀出来るのであれば、異なるアパートメントであろうが、直接生ポインタを操作しても問題ない(と分かっている)はず、という非常に厳しい制約の元でマーシャリングコストを0にすることも出来る、ということです。つまり、ただでさえ複雑なスレッドモデルを正しく理解する必要があるので、FTMやニュートラルを使う事は、よほどの理由がない限り避けた方が良いと言えます(せっかくCOMのランタイムが色々頑張ってくれる機能を放棄することに近しいです)。

DCOMへの拡張

ここまで見てきたアパートメントとマーシャリングの話が分かれば、「DCOM(Distributed COM)」は、延長上の技術でしかない事がわかります。

  • 今までの話は、プロセス内でのアパートメント境界を超える場合のマーシャリング処理ですが、
  • プロセス間通信にも応用できますね?(アウトプロセスCOMサーバー・サービス)
  • プロセス間通信が出来るなら、マシン間でも通信できるよね?(DCOM)

DCOMとは、要するにそういう事です。勿論、マシン境界をまたぐ場合は、セキュリティのトピック(認証と認可をどうするかなど)が存在しますが、基本は全く変わりません。


COMの将来展望(クロージング)

WP_20151219_18_26_52_Pro_LI時間が押してきたので、ほかにもトピックはあるのですがクロージングに入りました。

今回、主題として「All about COM」と銘打ったのですが、実は「隠された副題」がありました。それは、「COM requiem」です。COMの全盛期は2000年頃(つまりもう15年も前)と思っているのですが、まだまだいたるところで使われており、すぐにomitすることは当面出来そうにありません。しかし、プログラミング環境は完全に.NETが主体となり、C++でコードを書く際も、ライブラリのインフラとしてCOMを選択すると言う事は殆ど無くなりました。

自分なりの答えはあったのですが、「何故COMは失敗したのか」あるいは「何故COMは継続してメジャー足り得なかったのか」と言う事を、参加者に聞いてみたかったのです。

意見としては:

  • 設計思想は凄かった。特に「コンポーネント指向」として、処理系依存の排除やバイナリ互換性を維持する方法論、スレッド安全性をランタイムで担保する抽象性の高さ。
  • 「インターフェイス指向設計」に根差している。COMは実態のクラスに一切タッチしないので、インターフェイス分割設計と言う事に強く意識させられた。
  • やっぱり複雑すぎた。時代が早すぎた。複雑性を軽減する開発環境の補助も、現在と比べて全く不足していた。
  • .NETで複雑な処理も簡単に実装できるようになったので、相対的にCOMのランタイムが複雑に見えるようになった。
  • アパートメントという抽象性の高い概念が理解できなくて躓く。
  • COMを正しく実装するのが大変。VB6か、スクリプトコンポーネント(VBSやJSをCOMコンポーネントとして実行可能な、今考えるとやはり早すぎた感のある技術)でないと辛い。

これだけ複雑であるにも関わらず、COMを知っていて良かったと思える事として、やはり「インターフェイス」を強く意識する事になったことが、数人の意見で一致しました。この経験が、現在の.NETのインターフェイスや、ウェブシステムでのAPI設計の粒度やさじ加減と言った点にとても役立っています。

これは、COMのインターフェイスがほぼ最小のメソッドレベルの粒度で公開可能で、しかもDCOMによってマシン間のRPC呼び出しへの簡単にスケールアップ出来るにも関わらず、その細かい(Chatty)呼び出しが、システムのパフォーマンスと安定性に問題を起こす事が経験として理解できたと言う事です。

なので、COMはとても良い経験を与えてくれた良き技術・通過点でした。そろそろ卒業の頃だと思います。

個人的な思い

このブログでも、COMの解説を試みた記事を書いています(未完)。

COMのアパートメント (1) スレッド同期の隠蔽
COMのアパートメント (2) コンポーネントファサード
COMのアパートメント (3) スレッドの属性とコンポーネント
COMのアパートメント (4) スレッド親和性
COMのアパートメント (5) CoInitializeExは初期化ではない
COMのアパートメント (6) アパートメントの種類はどのように決まるのか

これが未完となっていたのと、内容としてこなれていないのが、ずっと喉の奥に引っかかっていたのです。今回のChalkTalkで完全に咀嚼して、完了させたかった。その目的は達成できたかなと。やってよかったなと思います。

COMは愛すべき対象でしたね。「ナナリィ…」って感じです。

次回もよろしくお願いします!


追記

ここで扱っていない、COMの重要な要素として以下のものがあります。メモとして残しておきます。

  • インターフェイスの集約(Aggregation)
  • IDispatchインターフェイス・スクリプティングサポート・イベントソースインターフェイス
  • モジュール生存期間の管理(ATLを使うなら、自動でやってくれる)
  • フリースレッドマーシャラー(FTM・FTMを集約すると、ニュートラルスレッドモデルと同じように振る舞える)
  • モニカ(IMoniker)
  • 情報管理(IStorage・IStream・IPropertyBag)
  • インターフェイスポインタのキャッシュや動的生成
  • ActiveX Controlとは
  • COM+とステートレス設計