C++/CLI でEventHandler/関数ポインタ変換用の便利クラスを作る

C++/CLI でコールバック用関数ポインタの生成、およびそれをC#で定義された　EventHandler へ購読させるノート。

1. native DLL はコールバック関数を渡すようなAPIとなっている。  
イベントが発生したら、そのコールバックにデータが渡される。
2. Csharp のインターフェイスコードは、普通のEventHander<xxxArgs> でイベントを公開するようなインターフェイス。

上記２つを結合させる部分のイベントハンドラー部分を実装するときのノート。

### インターフェイス

以下のC宣言があるとする。

``` C:
/** MyDLL */
/** コールバック定義
* @param [in] val 何か更新された値（適当な値）
* @param [in] lparam よくあるコールバックのタグ用のポインタ。
 */
typedef void (__stdcall* MyEventCallbackFunc)(int val, void* lparam);

/** 生成関数
* @param [in] init 何か適当な初期化用の値。
* @param [in] callback イベントが発生したら呼ばれるコールバック関数。
* @param [in] lparam callback を呼び出すときに渡すパラメータ。
* @return 
*/
void* __stdcall MyCreateFunc(
  int init,
  MyEventCallbackFunc callback,
  void* lparam);

/** 破棄関数
* @param [in] ptr MyCreateFunc() で返されたポインタ。
*/
void __stdcall MyDeleteFunc(void*); 

/** 適当な操作
* @param [in] ptr MyCreateFunc() で返されたポインタ。
* @param [in] newValue 操作の引数。
*/
void __stdcall DoFunc(void* ptr, int newValue);

```
以下は、C#部分のインターフェイス。

```csharp:
// C#部分の宣言。
namespace pinky
{
  /// <summary>
  ///  イベントデータ。
  /// </summary>
  public class MyEventData : EventArgs
  {
    public MyEventData(int val)
    {
      this.Value = val;
    }
    // 更新された値。
    public int Value{get;private set;}
  }

  /// <summary>
  ///  処理対象のインターフェイス。
  /// </summary>
  public interface IMyInterface
  {
    // 値が更新されたら、これで通知される。
    event EventHandler<MyEventData> MyEventHandler;
    // 処理するときのインターフェイス。
    void DoFunc(int newValue);
  }
}
```

### Native 用のコールバック関数を C++/CLI で定義

まずは、コールバック用のクラス(C++/CLI)を用意します。イカを参考にしました。

* [`Marshal.GetFunctionPointerForDelegate()`](https://msdn.microsoft.com/ja-jp/library/at4fb09f.aspx) メソッドで managed の delegate を関数ポインタに変換する。
* [C#のdelegateをCLI経由でC++の関数ポインタに渡す](http://posaune.hatenablog.com/entry/20120309/1331313750)

#### コールバックと型宣言

```cpp
public ref class MyCallback1{
public:
  // native のコールバック型。
  using NativeCallbackFunc_t = void(__stdcall*)(int val, void* lparam);

  // NativeCallbackFunc_t に対応する、managed delegate
  // 引数＆戻りは、一致させること。
  delegate void ManagedCallbackFunc(int val, IntPtr lparam);

  // コールバック関数として呼び出されるメソッド。
  void MyManagedCallback(int val, IntPtr lparam)
  {
    // ここに、EventHandler の呼び出しを置く。
  }
};
```

#### Marshal::GetFunctionPointerForDelegate() でコールバック関数ポインタ生成

```cpp
public ref class MyCallback1{
public:
  // 省略

  // ctor
  MyEventHandler1()
  {
    // メンバー関数を、Delegateにする。
    _eventHandlerDelegate= gcnew ManagedCallbackFunc(
      this,
      &MyEventHandler1::MyManagedCallback);
    // delegate を、native の関数ポインタへ変換してもらう。
    _funcPtr = Marshal::GetFunctionPointerForDelegate(
      this->_eventHandlerDelegate);
  }

  // native 用のポインタ取得。
  NativeCallbackFunc_t GetCallbackFuncPtr()
  {
    return static_cast<NativeCallbackFunc_t >( _funcPtr.ToPointer());
  }

private:
  /** コールバックデリゲードの本体。
  作成後、GetFunctionPointerForDelegate() を利用して、これの関数ポインタを
  _funcPtr へと渡されます。
  */
  initonly EventHandlerDelegate^ _eventHandlerDelegate;
  
  /** コールバック関数ポインタ、を保持するIntPtr
  これを、Native 側にコールバック関数として渡します。
  */
  System::IntPtr _funcPtr;
};
```

#### イベントハンドラの用意


```cpp
public ref class MyCallback1{
public:
  // 省略
  // 省略2

  // Callback の引数から、EventHandler 用データを作成するための、変換関数。
  static MyEventData^ CreateEventArgFunc( int val, IntPtr lparam )
  {
    return gcnew MyEventData(val);
  }

  // 普通のイベントハンドラ型
  using EventHandlerDelegate = System::EventHandler<MyEventData^>^;

  event EventHandlerDelegate^ CallbackEventHandler
  {
    void add(EventHandlerDelegate^ eh){ _callbackEventHandler += eh; }
    void remove(EventHandlerDelegate^ eh){ _callbackEventHandler -= eh; }
  }
private:
  EventHandlerDelegate^ _callbackEventHandler;

  // "コールバック関数と型宣言" で出てきたメソッド
  void MyManagedCallback(int val, IntPtr lparam)
  {
    auto fp = _callbackEventHandler;
    if( fp == nullptr ) return;
    auto arg = CreateEventArgFunc(val,lparam);
    fp->Invoke(nullptr/*sender*/, arg);
  }
};
```

#### IMyInterface の実装 ~MyCallback1 の使い方サンプル

使い方サンプルとしての、IMyInterface の実装です。☆１で、native の関数ポインタを渡します。
☆２で、EventHandler として公開します。

```csharp

namespace pinky_impl{
  public ref class MyImpl : public IMyInterface
  {
    // Native 関数用のポインタ
    void* _ptr;

    // さっき書いたコールバックオブジェクト
    MyCallback1^ _callback;
  public:
    MyImpl(int init)
    {
      _callback = gcnew MyCallback1();
      // ☆１ Native へは、このように関数ポインタを渡す。
      _ptr = ::MyCreateFunc(init, _callback->GetCallbackFuncPtr(), nullptr);
    }
    virtual  ~MyImpl(){this->!MyImpl();}
    !MyImpl()
    {
      ::MyDeleteFunc(_ptr);
    }

  public:
    #pragma region IMyInterface 実装

    // ☆２ イベントの add/remove 先を、コールバックオブジェクトにする。
    event EventHandler<MyEventData^>^ MyEventHandler
    {
      void add(EventHandler<MyEventData^>^ eh)
      {
        _callback->CallbackEventHandler += eh;
      }
      void remove(EventHandler<MyEventData^>^ eh)
      {
        _callback->CallbackEventHandler -= eh;
      }
    }

    // 普通のインターフェイス実装コード
    void DoFunc(int newValue)
    {
      // native の方の関数へ転送。
      ::DoFunc(_ptr, newValue);
    }

    #pragma endregion IMyInterface 実装
  };
}

```

### TODO集

* Template で頑張る。（元実装はテンプレートだけど解説が面倒なので書きなおした。）
個人的にはこれのtemplate版で変換を楽にしてます。msさんが、C++/CLI 用のラムダを用意してさえくれたら完璧だったのに・・・。
* 戻り型のある関数のコールバックはどうしたらいいのか思いつかない。
* EventArgs はgenericにできる気がするけれど、コンパイラと心を通わせられなかったので諦めている。
* 自分の場合、NativeからManaged部分へ、面倒な型変換が必要だったので C++/CLI で実装しましたが、普通の型だけなら p/invoke だけで行ける気がします。

### そのほかハマった箇所のノート

* template にすると include しないといけない。(CLIが認識できないのかな？)
* コールバック関数の呼び出しを終わったあたりで、身に覚えのないランタイムエラーが出たら、呼び出し規約を間違えていないか確認。
* native 側がまだ生きている状態で、コールバック用デリゲートを削除すると、アクセスバイオレーションで死ぬ。破棄順番を間違えないようにする。
* native のコールバック呼び出しで、なるべくなら、BeginInvoke など一旦制御をOSに返してから Managed のイベント応答処理を行うとよさげ。主に、Native側のモジュールの再入で参照カウンタ―の管理などが面倒にならなくていい。

これを書いておいてなんですが、ウィンドウズなら、WPFのウィンドウにも、何も考えずにPostMessage してしまう方が、楽な気がします。