既存のC++ネイティブプロジェクトでC#マネージドコードを使う

#はじめに
こんにちは。Daddy's Officeの市川です。

私が10年以上開発を続けているWindowsPCを監視カメラシステムにする*「LiveCapture3」*。
先日、今後の拡張性や生産性を考慮して、内部処理を見直しました。

基本的には、C++で構築していた各機能を、C#で置き換える、という作業です。

個人的には今でもC++は好きな言語なのですが、Windowsアプリ開発を行う場合、APIや各種SDKの提供状況を考えると、C#に移行していかないと難しい状況です。

とはいっても、すべてをC#に移行するのは現実的ではありません。

そこで「C++からC#をコールする」方法を調べました。


#C++からC#をコールする方法

色々な方法がありますが、C#の生産性の高さを既存のC++ネイティブプロジェクトで利用したい、というのが大きな目的なので、C++/CLIは極力使いたくない。
C#の処理をCOM参照可能にする、というのもコード量が増えるしめんどくさい。


そこで、

**「C#処理をDLLで作成し、C++/CLIラッパープロジェクト経由で、C++ネイティブプロジェクトからコールする」**


という方法で対応しました。

これであれば、若干スマートさには欠けますが、既存のC++ネイティブプロジェクトを変更せず、生産性の高いC#で処理を記述できます。

構成としてはこんな感じです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/382663/bf2d689e-3ef7-5918-67b1-112faf38a877.png)


#実装（AES複号）

今回、AESの複号処理を追加する必要があったのですが、C++でAES複号処理を記述するのは非常に面倒。
そこで、この方法を使用して、AES複号処理をC#で記述し、それをネイティブC++からコールする形にしました。

### DLLプロジェクト(C#)

まず、AESの複号を行うC#のクラスを作成します。
プロジェクトをC#のクラスライブラリとして作成し、下記のようなクラスを定義します。

```C#:AES複号クラス(C#)
namespace AesCrypto
{
    public class AesDecoder
    {
        public void Decode(byte[] Key, byte[] IV, byte[] src, out byte[] dst)
        {
            AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
            aes.BlockSize = 256;
            aes.KeySize = 256;
            aes.IV = IV;
            aes.Key = Key;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            using (ICryptoTransform decrypt = aes.CreateDecryptor())
            {
                dst = decrypt.TransformFinalBlock(src, 0, src.Length);
            }
        }
    }
}
```

うーん、簡単です！

メソッドは**Decode**メソッドのみで、引数に、鍵と初期ベクタ、AESエンコードされたデータを渡すと、out引数にデコードされたデータが格納される感じです。

### DLLラッパープロジェクト(C++/CLI)

作成したDLLプロジェクトを参照に追加して、C++/CLIのDLLプロジェクトを作成します。

このプロジェクトの目的は、マネージドとアンマネージドの引数型変換と、参照で追加したAES複号クラスの生成とメソッドコールになります。

```C++:アンマネージド－マネージド引数型変換とC#クラスのコール（C++/CLI)
__declspec(dllimport) HRESULT DoAesDecode(
		LPBYTE pKey, DWORD dwKeyLength,
		LPBYTE pIV, DWORD dwIVLen,
		LPBYTE pSrcData, DWORD dwSrcDataLen,
		LPBYTE pDstData, LPDWORD pdwDstDataLen)
{
	HRESULT hr = S_OK;

	try {
		
		# C#クラスに渡すマネージド配列を作成
		array< Byte >^ key = gcnew array< Byte >(dwKeyLength);
		array< Byte >^ iv = gcnew array< Byte >(dwIVLen);
		array< Byte >^ src = gcnew array< Byte >(dwSrcDataLen);
		array< Byte >^ dst;

		# 引数のメモリ（アンマネージド）を、マネージド配列にコピー		
		Marshal::Copy((IntPtr)pKey, key, 0, dwKeyLength);
		Marshal::Copy((IntPtr)pIV, iv, 0, dwIVLen);
		Marshal::Copy((IntPtr)pSrcData, src, 0, dwSrcDataLen);

		# C#クラスを作成して、メソッドをコール
		AesDecoder^ decoder = gcnew AesDecoder();
		decoder->Decode(key, iv, src, dst);

		# 戻り値をアンマネージドメモリにコピー		
		pin_ptr<Byte> pinnedBuf = &dst[0];
		memcpy_s(pDstData, *pdwDstDataLen, pinnedBuf, dst->Length);
		*pdwDstDataLen = dst->Length;
	}
	catch (Exception^ e) {
		hr = E_FAIL;
	}

    return hr;
}
```

AES複号を行うので、扱うデータはメモリ上のバイナリ値になります。

当然、C++上で確保したメモリ領域のポインタをC#クラスに渡すことはできませんので、変換処理が必要になります。

まず、C#クラスに渡す為のByte配列を定義し、そこに、引数で渡されてきたメモリの中身をMarshal::Copyでコピーします。

引数の内容がコピーされたByte配列を引数にC#の複号クラスのメソッドをコールします。

結果はout引数のByte配列に確保されますので、一旦pin_ptrでピン止めしてからmemcpyで内容をコピーします。

C++/CLIは、（個人的には）あまり書きたくないので、極力、型変換処理のみに抑えるようにしています。

### メインプロジェクト(ネイティブC++)

あとは、作成したDLLラッパープロジェクト（C++/CLI)でExportされた関数をC、++ネイティブプロジェクトで呼び出すだけです。

下記のようなExport宣言を行います。

```C++:Export関数宣言(C++/CLI)
__declspec(dllexport) HRESULT DoAesDecode(	
					LPBYTE pKey, DWORD dwKeyLength, 
					LPBYTE pIV, DWORD dwIVLen,
					LPBYTE pSrcData, DWORD dwSrcDataLen, 
					LPBYTE pDstData, LPDWORD pdwDstDataLen);
```

この関数をC++ネイティブプロジェクト内でコールすると、AESの複号処理が実行されます。



#参考

以下の情報を参考にさせていただきました。

[Calling C# .NET methods from unmanaged C/C++ code][1]
[Visusal C++ネイティブプロジェクトからC#マネージドコードを使用する方法を不要な文章抜きで説明する][2]
[C#プログラミング解説　マーシャリング (Marshaling)][3]



[1]:https://www.codeproject.com/tips/695387/calling-csharp-net-methods-from-unmanaged-c-cplusp
[2]:http://tensor12321.hatenablog.jp/entry/2017/02/21/Visusal_C%2B%2B%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%8B%E3%82%89C%23%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%89%E3%82%B3%E3%83%BC
[3]:https://so-zou.jp/software/tech/programming/c-sharp/cpp/marshaling.htm