2017-02-27 77 views
0

所以我有一个C++函数,采用一个方法作为一个std函数采取原始指针并填充它并返回所有值的累积,我希望公开这到一个C#API。这里是我的方法:通过C#委托数组引用非托管代码通过函数指针

// 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时,数组始终为空。我是否正确地做这件事或者这是可能的?

注意:如果这是可能的,是否也可以直接从某种托管向量引用转换为非托管std ::向量引用,而不是原始指针,那会更好。

+2

呃,解决std :: bind错误是很少的喜悦。在func_ptr上投的是非常非常邪恶的。函数指针是'__stdcall',但模板需要'__cdecl'函数。你不能那样做。 –

+0

这绝对不是一个喜欢处理std函数和绑定在一般情况下,但我已经遍及SO和微软网站:[链接](https://msdn.microsoft.com/en-us/library/367eeye0( v = vs.140).aspx),我的方法显然是推荐的方法,但我试图委托一个函数,该函数需要一个数组引用,我找不到解决方案。我也尝试了其他调用约定(__clrcall,__cdecl),结果相同。 – mconte

+0

我在VS2015上运行这个,而不是2017,我不确定编译器错误是什么,如果有的话。我只是试图用尽可能少的代码重现这个问题。 – mconte

回答

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); 
+0

评论不适用于扩展讨论;这个谈话已经[转移到聊天](http://chat.stackoverflow.com/rooms/137210/discussion-on-answer-by-dn-passing-c-delegate-with-array-reference-to-unmanage) 。 –

1

GetFunctionPointerForDelegate您的代表类型将有一个双指针的参数。 C#相当于double*不是ref double[],它只是double[]

但是你有一个更大的问题,你的C#懒惰初始化函数期望在本机内存中创建一个双精度数组,并作为一个指针被当作C#double[](一个.NET数组)处理。但是传递的缓冲区不是.NET数组,从来就不是,并且永远也不可能是。它没有System.Object元数据。它没有Length字段。即使GetFunctionPointerForDelegate足够聪明,可以尝试创建一个与本机double*相匹配的.NET数组,但它不知道它应该从第二个参数中获取大小。所以,即使你在某个地方得到了非null数组,你也会立即开始获取索引超出范围的异常。

的选项有:

  1. 重构本地C++过程接受输入数据,而不是一个懒惰函数用于创建输入

  • 将C++/CLI Shim传递给包装器进程,该进程将同时满足C++和.NET合同。它可以创建一个正确大小的.NET数组,调用C#委托(不包括此处涉及的函数指针转换),然后将结果复制到本机缓冲区中。关于std::function的好处是,通过一个拥有gcroot<PopFunctionDoubleDelegate^>的状态函数并没有什么大不了的。如果你想变得很花哨,你可以重复使用相同的.NET数组来填充处理结果。
  • +0

    感谢您的意见。关于第2点,这听起来适合我的问题,你的意思是“传递一个拥有gcroot的有状态函子”? – mconte

    +0

    @mconte他意味着我用'InvokeDelegate'类显示的内容。 –

    +0

    @Ðаn您的方法需要通过“Invoke”将数组参数传递给gcroot委托函数,但我不想这样做。 C#层不应该提供任何数据,只是一个函数定义。 – mconte

    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(); 
    } 
    

    这里的“魔术”是,因为我们将成员数据传递(固定)到成员函数,所以我们可以忽略“非托管”参数并将托管数据发送回.NET。

    +1

    我的关键目标是能够接受来自C#的委托函数,该委托函数接受对CLI中双连续数据结构的引用,以便我可以创建一个本机非托管函数指针并将其绑定到std :: function,将它传递给C++本机函数(Process.increment)。该函数本身返回一个指针并不重要,但接受一个C#函数,它将一个数组(或其他相关数据结构)作为引用,所以我可以本地使用它,因此调用GetFunctionPointerForDelegate。 – mconte

    +0

    感谢您的参与,我认为自己处于正确的轨道上,但我编辑了原始帖子,以更好地解决我的问题。我不知道或关心我的原生Process类中正在处理的底层数据(我为了参数而让该类非常简单),但我仍然需要将委托C#函数传递给我的本机函数,唯一的原因是提供一种填充底层数据的方法。 – mconte

    +0

    你不会很好地解释**完全**你正在尝试做什么。上面的代码展示了如何通过'std :: function'使用.NET委托。 –