もくじ
やりたいこと
関数ポインタを受け取る関数をpinvokeでC#から使うときのお作法をメモしておきたい。
やり方
下記の流れでおこなう。
- C#の中に、関数ポインタの型に相当するdelegateを宣言する。
- それを、
[DllImport(・・
を付けたメソッド(関数)本体の宣言のところの引数で使用する。 - 使うときは、そこにメソッドなりラムダを渡してやる。
具体的には、下記のような感じ。
サンプル
C++側はdll。
1秒おきに関数ポインタで登録した関数を呼ぶ。
登録と秒数カウント開始のための関数と、カウント停止のための関数を持っておく。
C#側はコンソールアプリ。
C++がエクスポートしてる関数をpinvokeで呼ぶ。
カウント開始のための関数を呼ぶ際、ラムダでメソッドを記述し、登録する。
DelegateParamFunc.cpp
// VS2019 プロジェクトの設定で「プリコンパイル済みヘッダーを使用しない」にして、stdafx.hのincludeを削除する必要あり
#include <windows.h>
#include <stdio.h>
#include <thread>
#define VC_DLL_EXPORTS
#include "DelegateParamFunc.h"
// public variable
// なし
// private variable
void (*func)(int count);
std::thread th;
BOOL isStop = false;
int count = 0;
// public function
// 関数の登録とカウントの開始
void __cdecl StartCountDownAndRegisterFunction(void(*f)(int))
{
if (f == NULL)
return;
func = f;
isStop = false;
th = std::thread([]()
{
while (!isStop)
{
Sleep(1000);
count++;
if (func != NULL)
func(count);
}
});
}
// 関数の登録解除とカウントの停止
void __cdecl StopCountDownAndUnregisterFunction()
{
isStop = true;
count = 0;
func = NULL;
th.join();
}
DelegateParamFunc.h
#pragma once
#include <Windows.h>
// エクスポートとインポートの切り替え
#ifdef VC_DLL_EXPORTS
#undef VC_DLL_EXPORTS
#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)
#else
#define VC_DLL_EXPORTS extern "C" __declspec(dllimport)
#endif
// エクスポート関数のプロトタイプ宣言
VC_DLL_EXPORTS void __cdecl StartCountDownAndRegisterFunction(void(*f)(int));
VC_DLL_EXPORTS void __cdecl StopCountDownAndUnregisterFunction();
Program.cs
using System;
using System.Runtime.InteropServices;
namespace PInvokeTest_deleate_CsApp
{
class Program
{
static void Main(string[] args)
{
NativeMethods.StartCountDownAndRegisterFunction((count)=>
{
Console.WriteLine(count);
});
Console.ReadLine();
NativeMethods.StopCountDownAndUnregisterFunction();
Console.ReadLine();
}
}
public static class NativeMethods
{
// 引数で渡すメソッドの型をdelegateで宣言
public delegate void DELEGATE_PARAM_FUNC(int count);
// 関数本体の宣言
[DllImport("PInvokeTest_delegate.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static void StartCountDownAndRegisterFunction(DELEGATE_PARAM_FUNC func);
[DllImport("PInvokeTest_delegate.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static void StopCountDownAndUnregisterFunction();
}
}
こまかい話メモ
C++のプロジェクトをビルドするとき、「pch.hをインクルードしましたか?」みたいなエラーがでるので、プロジェクトの設定で「プリコンパイル済みヘッダーを使用しない」にして、stdafx.hのincludeを削除する必要あり。
dllのPJは、できたdllをC#のPJの出力フォルダにビルド後イベントでコピーしてやらないといけない。
copy /y $(TargetPath) $(SolutionDir)PInvokeTest_deleate_CsApp\bin\$(Platform)\$(Configuration)\
のような感じでコピーする。
コード