COM構造化ストレージの IStream 生ポインタを C# 側に引き渡す
COM の構造化ストレージのストリームインターフェースであるIStream を生ポインタで持っているとして、 それをC#側からアクセスしたい時。
.NET には、ネイティブのIStream インターフェースに対応する System.Runtime.InteropServices.ComTypes.IStream がある。
IStream の生ポインタを ComTypes.IStream に変換するには、System.Runtime.InteropServices.Marshal.GetTypedObjectForIUnknown メソッドを使えば良い。
C++/CLIのコード例
using namespace System; using namespace System::Runtime::InteropServices; ... IStream *pStream = NULL; // ... IStreamのポインタをどうにかして生成... // ComTypes::IStream へ変換 ComTypes::IStream^ stream = (ComTypes::IStream^)Marshal::GetTypedObjectForIUnknown( (IntPtr)pStream, ComTypes::IStream::typeid ); }
複数の.vcxproj間で特定のパスを共有する方法
環境
- Visual Studio 2017 C++開発
やりたいこと
- 巨大なコードベースでVC++のソリューションを分けて開発している時に、すべての .vcxproj でルートディレクトリのパスを共有したい ** 出力ディレクトリやINCLUDEパスなど
- ただし、環境変数は使いたくない。レポジトリの作業ディレクトリをどこにチェックアウトしても使えるようにしたい。
やり方
- コードベース共通のプロパティシート(.propファイル)を作成し、ユーザマクロでMSBuildの $(MSBuildThisFileDirectory) マクロを使うことで、.prop ファイルのあるディレクトリを使ったマクロが定義できる
- 作成したプロパティシート(.propファイル)を、各.vcxprojファイルに設定する
参考
CoTaskMemAllocのリークをIMallocSpy で検知する
MSVCでメモリリークチェックと言えば、CRT ライブラリを使用したメモリ リークの検出 が定番ですが、これだとCランタイム経由じゃない CoTaskMemAlloc, CoTaskMemReallocで確保したメモリのリークは検知できません。 そこで IMallocSpy interface (COM) を使います。
IMallocSpy を使用したリークチェック
CoTaskMemAlloc は結局 IMalloc::Allocを呼びに行くのですが、IMallocSpy を CoRegisterMallocSpy() 関数でプロセスに登録しておくと、IMallocの各種メソッドが呼ばれる前後にIMallocSpyの各メソッドが呼ばれるようになるので、そこに好きなコードを書けばリークチェックとかが出来るようになります。 コードは以下。
#include <ObjIdl.h> #include <algorithm> #include <iostream> #include <sstream> #include <unordered_map> using namespace std; // リークチェック用 class MallocSpy : public IMallocSpy { // メモリ割当情報. struct AllocInfo { size_t count = 0; // 割当番号 SIZE_T allocSize = 0; // 割当サイズ void set(SIZE_T size) { ++count; allocSize = size; } } m_allocInfo; using AllocMap = unordered_map<void*, AllocInfo>; AllocMap m_alloced; // アドレスから割当情報へのマップ public: bool NoLeak() const { return m_alloced.empty(); } // リーク情報を文字列にダンプする string DumpLeak() const { if (NoLeak()) { return ""s; } stringstream ss; vector<pair<void*, AllocInfo>> leaks(m_alloced.begin(), m_alloced.end()); std::sort(leaks.begin(), leaks.end(), [](const auto &lhs, const auto &rhs) { return lhs.second.count < rhs.second.count; }); ss << "CoTaskMem Leak!" << endl; for (auto& [k, v] : leaks) { ss << "{" << v.count << "} " << v.allocSize << " bytes at 0x" << k << endl; } ss << "Leak dump end." << endl; return ss.str(); } // IMallocSpyを実装. Pre/Post, Alloc/Free 系の中で割当情報を保存しておく. virtual SIZE_T STDMETHODCALLTYPE PreAlloc(_In_ SIZE_T cbRequest) override { m_allocInfo.set(cbRequest); return cbRequest; } virtual void *STDMETHODCALLTYPE PostAlloc(_In_ void *pActual) override { if (pActual) { m_alloced[pActual] = m_allocInfo; } return pActual; } virtual void *STDMETHODCALLTYPE PreFree(_In_ void *pRequest, _In_ BOOL fSpyed) override { m_alloced.erase(pRequest); return pRequest; } virtual void STDMETHODCALLTYPE PostFree(_In_ BOOL fSpyed) override { } virtual SIZE_T STDMETHODCALLTYPE PreRealloc(_In_ void *pRequest, _In_ SIZE_T cbRequest, _Outptr_ void **ppNewRequest, _In_ BOOL fSpyed) { if (pRequest) { m_alloced.erase(pRequest); } m_allocInfo.set(cbRequest); return cbRequest; } virtual void *STDMETHODCALLTYPE PostRealloc(_In_ void *pActual, _In_ BOOL fSpyed) { if (pActual) { m_alloced[pActual] = m_allocInfo; } return pActual; } virtual void *STDMETHODCALLTYPE PreGetSize(_In_ void *pRequest, _In_ BOOL fSpyed) { return pRequest; } virtual SIZE_T STDMETHODCALLTYPE PostGetSize(_In_ SIZE_T cbActual, _In_ BOOL fSpyed) { return cbActual; } virtual void *STDMETHODCALLTYPE PreDidAlloc(_In_ void *pRequest, _In_ BOOL fSpyed) override { return pRequest; } virtual int STDMETHODCALLTYPE PostDidAlloc(_In_ void *pRequest, _In_ BOOL fSpyed, _In_ int fActual) override { return fActual; } virtual void STDMETHODCALLTYPE PreHeapMinimize(void) override { } virtual void STDMETHODCALLTYPE PostHeapMinimize(void) override { } // IUnknown を適当に実装. virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { if (riid == IID_IMallocSpy) { *ppvObject = this; return S_OK; } return E_NOTIMPL; } virtual ULONG STDMETHODCALLTYPE AddRef(void) { return 1; } virtual ULONG STDMETHODCALLTYPE Release(void) { return 0; } }; int main() { MallocSpy spy; // プロセスに登録 ::CoRegisterMallocSpy(&spy); // p2だけリークさせる auto p = CoTaskMemAlloc(100); auto p2 = CoTaskMemAlloc(200); CoTaskMemFree(p); // リーク情報をダンプ cout << spy.DumpLeak() << endl; }
出力はこうなる。
CoTaskMem Leak! {2} 200 bytes at 0x00780C98 Leak dump end.
あとはGoogle Test のEventListenerなどの仕組みを使って単体テストの前後でリークチェックするなどしましょう。
なぜC#ではなくF#を使いたいのか : 構造化分析と関数型プログラミング #fsharp
この記事は F# Advent Calendar 2017 - Qiita の 11 日目の記事です。
本記事では、なぜ自分がC#ではなくF#を使いたいのかを記したいと思います。
この記事で言いたいこと
- コンピュータシステムの本質とは入力データを出力データに変換することだと思っているので、そのままシンプルにデータフローでモデリングしたいよ
- 関数型プログラミングとDFDは相性が良いよ
- DFDのデータ・ディクショナリは代数的データ型(F#ではレコードや判別共用体)でそのままコードに落とし込めるよ
- だからC#よりもF#を使いたいよ
コンピュータシステムとは何をするものか
我々ソフトウェアエンジニアは入力データを出力データへ変換するためにソフトウェアを作っています。
入力データは、UI越しのユーザ入力かもしれないし、ファイルから読み取るのかもしれないし、ネットワーク越しにくるのかもしれないし、OSのタイマーが一定間隔で発行するイベントかもしれません。
出力データは、UIでのユーザへの情報提示かもしれないし、ファイル、ネットワークなどへ送り出すのかもしれません。
つまり、ソフトウェアシステムとは、入力データを出力データに変換するプロセスです。そして、そのためのモデリングツールとして、何十年も前からあるのが構造化分析とデータフローダイアグラム(DFD)[1]です。
なお、近年では、リアクティブプログラミング用のライブラリ(Rxなど)によって、時系列の一連の入力データを、これまた時系列の出力データに変換するプロセスも宣言的に書けるようになってきています[2][3]。
関数型モデリングツールとしてのDFD
オブジェクト指向分析におけるモデリングツールとしてUMLがあります。では、関数型プログラミングにおいてモデリングに使うツールは何でしょうか。 関数型プログラミングでは、基本的に副作用のない関数を組み合わせてアプリケーションを作成します。副作用のない関数は入力を出力に変換するものなのでDFDと相性がよく、関数型プログラミング言語ではDFDをそのままコードに落とし込む事ができます。
データ・ディクショナリと代数的データ型
構造化分析とシステム仕様[1] によると、DFDでフロー中を流れるデータはデータ・ディクショナリに定義されます。そして、個々のデータは、要素の繰り返し・連結・選択によって定義されます。
名前 = 姓 and 名 分類 = 幼児 or 子供 or 大人
この構造は、そのまま代数的データ型(F#では判別共用体やレコード)で表す事ができます。
type 名前 = { 姓 : string; 名 : string } type 分類 = 幼児 | 子供 | 大人
DFDをコードに落とし込む際の、C#と比較したF#の利点
以前の記事 なぜC#よりもF#なのか 2017 #fsharp - p_tan's blog でもC#とF#を比較していますが、DFDをコードに落とし込むという観点からC#とF#を比較すると、F#には以下のようなメリットがあります
- ユーザ定義型を作成するのが簡単
- 粒度の細かな関数を組み合わせて大きな関数を作りやすい。すなわち、DFDのプロセスの分解と、関数の粒度を合わせやすい
- 上記の代数的データ型によって、データ・ディクショナリのコード化が楽
- パターンマッチでの網羅性判定により、機能追加時の実装忘れなども警告が出る
まとめ
関数型プログラミングは、「普通の」アプリケーションの開発をきっとシンプルにしてくれます。
業務でF#つかいたいなぁ・・・。
参考書籍
- [1]構造化分析とシステム仕様<新装版> : DFDの古典
- [2] 関数型リアクティブプログラミング : 関数型リアクティブプログラミングはDFDの実装方法の一つ
- [3] Dataflow and Reactive Programming Systems : データフローとリアクティブプログラミングについてまとめられている
レコードの不変条件を強制する方法 #fsharp
この記事は F# Advent Calendar 2017 - Qiita 10日目の記事です。
F#でレコードを使う場合に、型の不変条件を強制したい場合があったので調べました。
例 : Triangle 型
例えば、以下のような辺の長さ a, b , c を持つ三角形を表すレコードがあったとします。
module Shape = type Triangle = { a : float; b : float; c : float }
三角形の辺の長さ a, b, c の不変条件として、以下をすべて満たしている必要があります。
- a + b > c
- b + c > a
- c + a > b
さて、今のままでは不変条件を無視してTriangle の値を生成できてしまいます。
open Shape let tri = { Triangle.a = 1.; b = 2.; c = -1. } // 不正な値での生成
クラスにしてコンストラクタやファクトリメソッドでチェックするという手もありますが、レコードや判別共用体には、クラスにない以下のメリットがあります。
- デフォルトでの値による等価性/比較判定
- パターンマッチによる値の取り出し、分解
上記のメリットを残したまま、不変条件を強制する方法はないでしょうか。
方法1. フィールドの private 定義とアクティブパターンを組み合わせる
なぜかMSDNには書いていないのですが、レコードや判別共用体は型自体のアクセス指定とは別に、フィールドのprivate/internal定義が可能になっています。フィールドをprivateにすると、モジュール外からは生成できなくなります。ただ、パターンマッチもできなくなってしまうので、アクティブパターンを用いてパターンマッチできるようにします。
module Shape = // フィールドのみprivate定義. 型はpublicのまま type Triangle = private { a : float; b : float; c : float } // ファクトリ関数 let createTriangle a b c : Triangle option = // 不変条件チェック let isValid a b c = a + b > c && b + c > a && c + a > b if isValid a b c then Some { Triangle.a = a; b = b; c = c } else None // アクティブパターンでパターンマッチでの分解を可能にする let (|Triangle|) {Triangle.a = a; b = b; c = c} = (a, b, c)
上記のように定義すると、以下のように使えます。
open Shape // { Triangle.a = 1.; b = 2.; c = -1. } // privateにより直接生成不可 createTriangle 1. 2. -1. // -> 不変条件に違反しているのでNoneを返す. let tri = createTriangle 2. 3. 4. |> Option.get let (Triangle(a,b,c)) = tri // パターンマッチで分解.
かなりいい感じなのですが、わざわざアクティブパターンを書かなければならないのと、一部のフィールドのみを用いたパターンマッチができなくなることが欠点です。
let { Triangle.a = a } = tri // 通常のレコードなら可能だが・・・
方法2. クラスのvalメンバとStructuralEquality, StructuralComparison 属性を組み合わせる
もう一つの方法は、クラスのvalフィールドと属性を組み合わせるものです。(すでにレコードじゃなくなっていますが)
module Shape = [<StructuralEquality>] [<StructuralComparison>] [<Struct>] // 現状、上記2つの属性は Struct でないとつけられない type Triangle = val a : float val b : float val c : float private new(a, b, c) = { a = a; b = b; c = c } // ファクトリ関数 static member Create a b c : Triangle option = // 不変条件チェック let isValid a b c = a + b > c && b + c > a && c + a > b if isValid a b c then Some(Triangle(a, b, c)) else None
クラスのvalフィールドはレコードのフィールドのようにパターンマッチができます。
open Shape let tri = Triangle.Create 3. 4. 5. |> Option.get let {Triangle.a = a} = tri // フィールドの一部をパターンマッチできる
また、StructuralEquality, StructuralComparison属性をつけることで、値での等価性/比較判定ができるようになります。 ただし、現状ではStructクラスにしか上記属性はつけられないようです。Structはデフォルトコンストラクタですべてのフィールドを0初期化できてしまうので、不変条件を強制するという今回の趣旨からみると残念な感じです。
なお、Structではない通常のクラスにも、StructuralEquality, StructuralComparison属性をつけられるようにしてほしいというIssueが立っているようです[6]。
まとめ
フィールドのアクセス指定とコンストラクタのアクセス指定は別々にしたい。
参考サイト
- [1] 三角形の成立条件とその証明 | 高校数学の美しい物語
- [2] f# - Is it possible to enforce that a Record respects some invariants? - Stack Overflow
- [3] constructor - How to do argument validation of F# records - Stack Overflow
- [4] Designing with types: Single case union types | F# for fun and profit
- [5] New type: constrained type · Issue #553 · fsharp/fslang-suggestions · GitHub
- [6] Implement [<StructuralEquality>] and [<StructuralComparison>] for simple class types · Issue #554 · fsharp/fslang-suggestions · GitHub
PODクラスのデフォルトコンストラクタとメンバ変数をprivateにすることでクラスの不変条件を担保する
C++でCと互換性のあるデータ型を使いたい場合は、POD(Plain Old Data)型としなければならない。 C++11 でPODクラスの要件が緩和されたので、PODクラスのメンバ変数やdefaultコンストラクタをprivate に定義できるようになった。 これにより、PODクラスでもコンストラクタでクラスの不変条件を担保できる。
C++11 でのPODクラスの要件
C++ concepts: PODType - cppreference.com から引用。
a trivial type;
a standard layout type;
has no non-static members that are non-POD;
POD クラスに対する不変条件の強制
上記要件さえ満たしていれば、メンバ変数がprivate でもいい。また、trivial class の要件はデフォルトコンストラクタがtrivial であることなので、privateかつ default 指定でデフォルトコンストラクタを定義すれば良い。
#include <type_traits> #include <stdexcept> struct A { private: int a_; // メンバがprivate でもいい. A() = default; // デフォルトコンストラクタがprivateでも default 定義であればいい. public: // デフォルトコンストラクタがtrivial なら他のコンストラクタがあってもいい. A(int a) : a_(a){ // メンバ a_ は非負という条件を強制. if(a_ < 0){ throw std::invalid_argument("a must not be negative"); } } }; static_assert(std::is_pod_v<A>); // A は POD.
なお、C++20 では PODという概念は deprecated になる模様。