0

So I have a c++ function that takes a method as a std function which take a raw pointer and populates it and returns the accumulation of all values, and I wish to expose this to a C# API. Here's my approach:

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

Followed by a cli wrapper to expose this to 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);

};

with implementation:

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

And a final C# sample application to provide a delegate function used to fill data, (e.g. populate_func parameter for process_accumulate):

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

But the array is always null when passed to PopulateFunctionDoubleIncr on the C#. Am I doing this correctly or is this possible?

Note: if this is possible, is it also possible to directly convert from some sort of managed vector reference to an unmanaged std::vector reference instead of raw pointers, that would be even better.

3
  • 2
    Ugh, troubleshooting std::bind errors is very little joy. That cast on func_ptr is very, very evil. The function pointer is __stdcall but the template expects a __cdecl function. You can't do that. Commented Feb 27, 2017 at 20:12
  • It's definitely not a joy dealing with std function & bind in general, but I've looked all over SO and the microsoft site: link and my approach is apparently the recommended approach, but I'm trying to delegate a function that takes an array reference for which I can't find a solution. I've also tried other calling conventions (__clrcall, __cdecl) with the same result. Commented Feb 27, 2017 at 20:49
  • I'm running this on VS2015, not 2017, I'm not sure what compiler errors are, if any. I'm just trying to reproduce this issue with as little code as possible. Commented Feb 27, 2017 at 23:12

3 Answers 3

2

Based on your comment, I think you're trying to do something like this:

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

You then want to eventually call f() from C# as follows:

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

One way to do that is by using a lambda and gcroot in C++/CLI:

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

The "magic" here is that since we passing (pinned) member data to a member function, we can just ignore the "unmanaged" arguments and send the managed data back to .NET.

Sign up to request clarification or add additional context in comments.

6 Comments

My key goal is to be able to accept a delegate function from C# that accepts a reference to a contiguous data structure of doubles into CLI so that I can create a native unmanaged function pointer from that and bind it to a std::function and pass it to a C++ native function (Process.increment). That function itself returning a pointer or not doesn't really matter, but accepting a C# function that takes an array (or other relevant data structure) as a reference so I can use it natively, hence the GetFunctionPointerForDelegate call.
Thanks for the input, I think I'm on the right track, but I edited my original post to better address my problem. I don't know or care about the underlying data being process in my native Process class (I made the class very simple for arguments sake), but I still need to pass a delegate C# function to my native function for the sole reason to provide a method to populate the underlying data.
Funny, you did exactly what I suggested in my bullet point #2, except for the part about actually working on the correct data. And I got a downvote for my effort?
The problem with your solution is that the C# layer must pass the array data parameter into the CLI layer but I don't want to do that, only the native code should do that via the Process.increment method. I just want my native code to use a C# function to modify a reference to an array, or other appropriate data structure. I've updated my original post to reflect this.
No, I want data to live in my unmanaged code. I want to provide a C# function interface for populating data in my native class before doing some hefty processing. I wrote a simple increment process to make it easier to understand.
|
1

[ I'm writing a new question since this seems to be different enough. ]

As I said, I've already given you all of the pieces to put this together (assuming it's now clear what you're trying to do). From my comment in the other answer, you just need a .NET wrapper around a C-style array. Maybe there's a nice way to do this (SafeBuffer?), but it's easy enough to write your own:

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

Now, you hook the two pieces together, just as before with an InvokeDelegate helper class:

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

The rest of the My namespace is:

namespace My
{
    public delegate void PopFunctionDoubleDelegate(DoubleArray^);
    public ref struct ProcessDoubleCLI abstract sealed
    {
        static double add_cli(PopFunctionDoubleDelegate^ delegate_func);
    };
}

with add_cli pretty much as before:

double My::ProcessDoubleCLI::add_cli(My::PopFunctionDoubleDelegate^ delegate_func)
{
    InvokeDelegate invokeDelegate(delegate_func);
    return invokeDelegate.invoke();
}

Using this from C# is now:

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

Edit since you seem to really want to pass the delegate as a function pointer, here's how to do that:

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

But, as I said, that's hard to work with in C# because your code is

public void PopulateFunctionPtrIncr(IntPtr pData, int count)
{
}
My.PopFunctionPtrDelegate ptrFunc = dr.PopulateFunctionPtrIncr;
result = My.ProcessDoubleCLI.add_cli(ptrFunc);

1 Comment

Comments are not for extended discussion; this conversation has been moved to chat.
1

GetFunctionPointerForDelegate on your delegate type is going to have a double-pointer for the parameter. The C# equivalent of double* is not ref double[], it is just double[].

But you have an even bigger problem, your C# lazy initialization function is expecting a double array created in native memory and passed as a pointer to be treated as a C# double[] (a .NET array). But the buffer passed isn't a .NET array, never was, and never can be. It doesn't have the System.Object metadata. It doesn't have a Length field. Even if GetFunctionPointerForDelegate were smart enough to try to create a .NET array matching the native double*, it wouldn't know that it is supposed to get the size from the second argument. So even if you got a non-null array out of somewhere, you would immediately start getting index-out-of-bounds exceptions.

Your options are:

  1. Refactor the native C++ process to accept input data rather than a lazy function for creating the input

or

  1. Pass a C++/CLI shim to the wrapper process, which will satisfy both the C++ and .NET contracts. It can create a .NET array of the correct size, call the C# delegate (no conversion to function pointer involved here), then copy the results into the native buffer. The good thing about std::function is that passing a stateful functor that possesses a gcroot<PopFunctionDoubleDelegate^> is no big deal. If you want to get really fancy, you can reuse the same .NET array to fill in with the process results.

3 Comments

Thanks for the input. Regarding point 2,which sounds appropriate for my issue, how do you mean "passing a stateful functor that possesses a gcroot<PopFunctionDoubleDelegate^>"?
@Ðаn Your approach requires the array parameter to be passed to the gcroot delegate function via "Invoke" but I don't want to do this. The C# layer should not provide any data, just a function definition.
@Ben I was afraid of this. I don't wish to allocate more data than I need nor directly provide data since I do not want to expose my C++ native buffers to the CLI/C# layer. I want high cohesion, just a function definition as a form of communication. It's starting to sound what I specifically want isn't possible based on what you say.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.