![](/img/trans.png)
[英]Passing a Delegate to a callback function in unmanaged code using c#
[英]Passing C# delegate with array reference to unmanaged code via function pointer
因此,我有一個c ++函數,該函數將一個方法作為一個std函數,該函數采用一個原始指針並將其填充並返回所有值的累加,我希望將其公開給C#API。 這是我的方法:
// acc_process.h
#include <vector>
#include <numeric>
template<typename T>
static T process_accumulate(std::function<void(T*, size_t)> populate_func)
{
std::vector<T> data(1000);
populate_func(data.data(), data.capacity());
return std::accumulate(data.begin(), data.end(), 0);
}
隨后是cli包裝器,以將其公開給C#:
// acc_process_cli.h
#include acc_process.h
#pragma managed(push, off)
typedef void(__stdcall *ReadCallbackDouble)(double* data, size_t data_size);
#pragma managed(pop)
delegate void PopFunctionDoubleDelegate(cli::array<double> ^ % data, size_t data_size );
public ref class ProcessDoubleCLI {
public:
ProcessDoubleCLI() {};
double accumulate_cli(PopFunctionDoubleDelegate^ delegate_func);
};
與實施:
// acc_process_cli.cpp
#include "acc_process_cli.h"
double ProcessDoubleCLI::accumulate_cli(PopFunctionDoubleDelegate^ delegate_func)
{
IntPtr FunctionPointer =
Marshal::GetFunctionPointerForDelegate(delegate_func);
ReadCallbackDouble func_ptr = static_cast<ReadCallbackDouble>(FunctionPointer.ToPointer());
auto func_bind = std::bind(
(void (*)(double* , size_t))func_ptr, std::placeholders::_1, std::placeholders::_2);
return process_accumulate<double>(func_bind);
}
最后一個C#示例應用程序提供了一個用於填充數據的委托函數(例如process_accumulate的populate_func參數):
// test.cs
class DoubleReader {
public static void PopulateFunctionDoubleIncr(ref double[] data, ulong size_data) {
ulong idx = 0;
ulong size = size_data;
while (idx < size) {
data[idx] = (double)idx;
++idx;
}
}
static void Main(string[] args)
{
DoubleReader dr = new DoubleReader();
PopFunctionDoubleDelegate func = PopulateFunctionDoubleIncr;
ProcessDoubleCLI process = new ProcessDoubleCLI();
double acc_value = process.accumulate_cli(func);
}
}
但是,當將數組傳遞給C#上的PopulateFunctionDoubleIncr時,該數組始終為null。 我是正確執行此操作還是有可能?
注意:如果可能,是否還可以直接從某種托管向量引用轉換為非托管std :: vector引用而不是原始指針,這樣會更好。
根據您的評論,我認為您正在嘗試執行以下操作:
static void f(std::function<void(double*, size_t)> cb, double* data, size_t data_size) // existing code, can't change
{
cb(data, data_size);
std::for_each(data, data+data_size, [](double &n) { n += 1; });
}
然后,您最終希望從C#調用f()
,如下所示:
class DoubleReader
{
public void PopulateFunctionDoubleIncr(double[] data)
{
int idx = 0;
while (idx < data.Length)
{
data[idx] = (double)idx;
++idx;
}
}
}
class Program
{
static void Main(string[] args)
{
var data = new double[1000];
DoubleReader dr = new DoubleReader();
My.PopFunctionDoubleDelegate func = dr.PopulateFunctionDoubleIncr;
My.add_cli(data, func);
}
}
一種方法是在C ++ / CLI中使用lambda和gcroot
:
public ref struct My
{
delegate void PopFunctionDoubleDelegate(cli::array<double>^ data);
static void add_cli(cli::array<double>^ data, PopFunctionDoubleDelegate^ callback);
};
class InvokeDelegate
{
msclr::gcroot<cli::array<double>^> _data;
msclr::gcroot<My::PopFunctionDoubleDelegate^> _delegate;
void callback()
{
_delegate->Invoke(_data);
}
public:
InvokeDelegate(cli::array<double>^ data, My::PopFunctionDoubleDelegate^ delegate)
: _data(data), _delegate(delegate) { }
void invoke()
{
cli::pin_ptr<double> pPinnedData = &_data[0];
double* pData = pPinnedData;
f([&](double*, size_t) { callback(); }, pData, _data->Length);
}
};
void My::add_cli(cli::array<double>^ data, PopFunctionDoubleDelegate^ callback)
{
InvokeDelegate invokeDelegate(data, callback);
invokeDelegate.invoke();
}
這里的“魔術”是,由於我們將成員數據傳遞(固定)到成員函數,因此我們可以忽略“非托管”參數,並將托管數據發送回.NET。
您的委托類型上的GetFunctionPointerForDelegate
將為該參數提供一個雙指針。 C#中double*
等效項不是ref double[]
,而只是double[]
。
但是您還有一個更大的問題,您的C#延遲初始化函數期望在本機內存中創建一個double數組,並將其作為指針傳遞為C# double[]
(。NET數組)。 但是傳遞的緩沖區不是.NET數組,從來沒有,而且永遠也不能 。 它沒有System.Object
元數據。 它沒有“ Length
字段。 即使GetFunctionPointerForDelegate
足夠聰明,可以嘗試創建與本機double*
相匹配的.NET數組,它也不知道應該從第二個參數中獲取大小。 因此,即使您從某個地方獲取了非null數組,您也將立即開始獲取index-out-of-bounds異常。
您的選擇是:
要么
std::function
的好處是,傳遞一個擁有gcroot<PopFunctionDoubleDelegate^>
的有狀態函子沒什么大不了的。 如果您真的想花哨的話,可以重用相同的.NET數組來填充處理結果。 [我正在寫一個新的問題,因為這似乎已經足夠不同了。 ]
正如我說的,我已經為您提供了將其組合在一起的所有方法(假設現在可以清楚地知道您要做什么)。 根據我在另一個答案中的評論,您只需要一個圍繞C樣式數組的.NET包裝器即可。 也許有一種不錯的方法( SafeBuffer
?),但是編寫自己的方法很容易:
namespace My
{
public ref class DoubleArray
{
double* m_data;
size_t m_size;
public:
DoubleArray(double* data, size_t size) : m_data(data), m_size(size) {}
property int Length { int get() { return m_size; }}
void SetValue(int index, double value) { m_data[index] = value; }
};
}
現在,就像之前使用InvokeDelegate
幫助器類一樣,將這兩部分連接在一起:
class InvokeDelegate
{
msclr::gcroot<My::PopFunctionDoubleDelegate^> _delegate;
void callback(double* data, size_t size)
{
_delegate->Invoke(gcnew My::DoubleArray(data, size));
}
public:
InvokeDelegate(My::PopFunctionDoubleDelegate^ delegate) : _delegate(delegate) { }
double invoke()
{
return process_accumulate<double>([&](double* data, size_t size) { callback(data, size); });
}
};
My
名稱空間的其余部分是:
namespace My
{
public delegate void PopFunctionDoubleDelegate(DoubleArray^);
public ref struct ProcessDoubleCLI abstract sealed
{
static double add_cli(PopFunctionDoubleDelegate^ delegate_func);
};
}
與add_cli
幾乎一樣:
double My::ProcessDoubleCLI::add_cli(My::PopFunctionDoubleDelegate^ delegate_func)
{
InvokeDelegate invokeDelegate(delegate_func);
return invokeDelegate.invoke();
}
現在使用C#中的代碼:
class DoubleReader
{
public DoubleReader() { }
public void PopulateFunctionDoubleIncr(My.DoubleArray data)
{
int idx = 0;
while (idx < data.Length)
{
data.SetValue(idx, (double)idx);
++idx;
}
}
static void Main(string[] args)
{
DoubleReader dr = new DoubleReader();
My.PopFunctionDoubleDelegate func = dr.PopulateFunctionDoubleIncr;
var result = My.ProcessDoubleCLI.add_cli(func);
Console.WriteLine(result);
}
}
進行編輯,因為您似乎真的想將委托作為函數指針傳遞,這是這樣做的方法:
public delegate void PopFunctionPtrDelegate(System::IntPtr, int);
double My::ProcessDoubleCLI::add_cli(My::PopFunctionPtrDelegate^ delegate_func)
{
auto FunctionPointer = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(delegate_func);
auto func_ptr = static_cast<void(*)(double*, size_t)>(FunctionPointer.ToPointer());
return process_accumulate<double>(func_ptr);
}
但是,正如我所說,在C#中很難使用它,因為您的代碼是
public void PopulateFunctionPtrIncr(IntPtr pData, int count)
{
}
My.PopFunctionPtrDelegate ptrFunc = dr.PopulateFunctionPtrIncr;
result = My.ProcessDoubleCLI.add_cli(ptrFunc);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.