通过函数指针

时间:2017-02-27 19:17:37

标签: c# visual-c++ c++-cli

所以我有一个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引用而不是原始指针,这样会更好。

3 个答案:

答案 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数组,也不会知道它应该从第二个参数获得大小。因此,即使你从某个地方获得了一个非空数组,你也会立即开始获得索引越界异常。

您的选择是:

  1. 重构本机C ++进程以接受输入数据而不是用于创建输入的惰性函数
    1. 将C ++ / CLI填充程序传递给包装器进程,该进程将满足C ++和.NET协定。它可以创建一个正确大小的.NET数组,调用C#委托(不转换到此处涉及的函数指针),然后将结果复制到本机缓冲区。关于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);