所以我有一个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引用而不是原始指针,这样会更好。
答案 0 :(得分:2)
根据您的评论,我认为您正在尝试执行以下操作:
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();
}
&#34;魔法&#34;这是因为我们将成员数据传递(固定)到成员函数,我们可以忽略&#34; unmanaged&#34;参数并将托管数据发送回.NET。
答案 1 :(得分:1)
GetFunctionPointerForDelegate
将具有参数的双指针。 double*
的C#等效值不是ref double[]
,只是double[]
。
但是你有一个更大的问题,你的C#延迟初始化函数期望在本机内存中创建一个双数组,并作为指针传递给C#double[]
(一个.NET数组)。但传递的缓冲区不是.NET数组,从来没有,也永远不会。它没有System.Object
元数据。它没有Length
字段。即使GetFunctionPointerForDelegate
足够聪明,可以尝试创建与本地double*
匹配的.NET数组,也不会知道它应该从第二个参数获得大小。因此,即使你从某个地方获得了一个非空数组,你也会立即开始获得索引越界异常。
您的选择是:
或
std::function
的好处是传递拥有gcroot<PopFunctionDoubleDelegate^>
的有状态仿函数并不是什么大问题。如果你想变得非常花哨,可以重复使用相同的.NET数组来填充过程结果。答案 2 :(得分:1)
[我正在写一个新问题,因为这似乎有点不同。 ]
正如我所说的那样,我已经把所有的东西都放在了一起(假设它现在已经清楚了你要做的事情)。从我在另一个答案中的评论来看,你只需要一个围绕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);