簡體   English   中英

通過函數指針將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異常。

您的選擇是:

  1. 重構本機C ++進程以接受輸入數據,而不是用於創建輸入的惰性函數

要么

  1. 將C ++ / CLI填充程序傳遞給包裝程序,這將同時滿足C ++和.NET合同。 它可以創建正確大小的.NET數組,調用C#委托(此處不涉及對函數指針的轉換),然后將結果復制到本機緩沖區中。 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM