C++/CLI marshaling .NET delegate to native delegat

2019-09-05 03:12发布

问题:

I am trying to pass a delegate with managed parameters to native code to be invoked. My code below runs ok, but the string output is garbage.

Native Class

Header

#pragma once
typedef void (* SegmentCreatedDelegate)(char** arg);
public class SampleClass
{
public:
    SampleClass(void);
    ~SampleClass(void);
    void DoWork(SegmentCreatedDelegate callback);
};

Code

SampleClass::SampleClass(void)
{
}

SampleClass::~SampleClass(void)
{

}

void SampleClass::DoWork(SegmentCreatedDelegate callback)
{
    for(int x = 0; x< 10; x++)
    {
        char* myStr2 = "newsegment!";
        callback(&myStr2);
    }
}

Managed Class

Header

#pragma once
public ref class SampleClassNet
{
public:
    delegate void SegmentCreatedDelegateNet(System::String^ arg);
    SampleClassNet(void);
    void DoWork(SegmentCreatedDelegateNet^ segmentCreatedCallback);
};

Code

SampleClassNet::SampleClassNet(void)
{
}

void SampleClassNet::DoWork(SegmentCreatedDelegateNet^ segmentCreatedCallback)
{
    SampleClass* nativeClass = new SampleClass();
    System::IntPtr pointer = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(segmentCreatedCallback);
    nativeClass->DoWork((SegmentCreatedDelegate)(void*)pointer);
    System::GC::KeepAlive(segmentCreatedCallback);
}

This code runs fine with the follow c#.

var sampleClass = new SampleClassNet();
sampleClass.DoWork((Console.WriteLine));

Except I get the following output, instead of the expected 10 entries of "newsegment!".

(ÇÆX
(ÇÆX☺
(ÇÆX☻
(ÇÆX♥
(ÇÆX♦
(ÇÆX♣
(ÇÆX♠
(ÇÆX
(ÇÆX
(ÇÆX

Not exactly "newsegment!", but I am not sure why the marshaling is not working. Maybe I need I need some kind of "MarshalAs" attribute so that the System::String knows that I have 8-bit chars?

回答1:

As mentioned in the comments, you should convert the char** to a String^. (Btw, why pass char**, not char*? String has a constructer taking char*, which might simplify things a lot.)

I haven't tried the following, but you might give it a try:

public ref class SampleClassNet {
private:
    delegate void SegmentCreatedDelegateNative(char** str);
    SegmentCreatedDelegateNet^ managedCallback;
    SegmentCreatedDelegateNative^ nativeCallback;
    void printString(char** string);
public:
    delegate void SegmentCreatedDelegateNet(System::String^ arg);
    SampleClassNet();
    void DoWork(SegmentCreatedDelegateNet^ segmentCreatedCallback);
};

SampleClassNet::SampleClassNet() {
    nativeCallback = printString;
}

void SampleClassNet::DoWork(SegmentCreatedDelegateNet^ segmentCreatedCallback) {
    SampleClass* nativeClass = new SampleClass();
    managedCallback = segmentCreatedCallback;
    System::IntPtr pointer = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(nativeCallback);
    nativeClass->DoWork((SegmentCreatedDelegate)(void*)pointer);
}

void SampleClassNet::printString(char** string) {
    if (this->managedCallback != nullptr) {
        String^ str = gcnew String(*string);
        managedCallback(str);
    }
}

The basic idea is to use another delegate, SegmentCreatedDelegateNative, handed to the native class, and to call the actual managed delegate from the function associated with the wrapper.