使用C#P / Invoke对方法进行编组

时间:2011-12-16 10:55:12

标签: c# c++ interop pinvoke marshalling

我正在尝试PInvoke到这个C ++库函数:

int Initialize(Callback* callback);

struct Callback
{
    //.......
    void virtual StateChanged(int state) = 0;
};

我尝试过这种天真的C#代码,但它不起作用。

[DllImport("CPlusPlusLibrary.dll", SetLastError = true, EntryPoint = "?Initialize@CPlusPlusLibrary@@YAHPAUCallback@1@@Z")]
public static extern int Initialize(Callback callback);

[StructLayout(LayoutKind.Sequential)]
public class Callback
{
    //....
    public delegate void IntDelegate(int state);
    public IntDelegate StateChanged(int state);
}
var callback = new Callback();
var result = Initialize(callback);

3 个答案:

答案 0 :(得分:3)

据我所知,这样做是不可能的。方法不像字段那样“在那里”,除此之外,使用虚方法创建结构将在对象中创建vtable指针,而不是在c#镜像类中考虑。

你可以做的是PInvoke方法,它接受一个functionPointer(在C ++中)并在那里传递一个委托(C#)。然后,您可以使用此函数指针从本机代码中调用它,您的代理将启动。

然后,您可以更改stateChange方法定义以将Callback *作为第一个参数,因此当您从本机代码调用它时,您可以传递一个对象指针,该指针负责该更改并将其编组回c#中的Callback。

//编辑没有本机dll的源代码,在c#和c ++之间构建桥梁是我的想法。这可以用c ++ / cli或c ++来完成,坚持原生我的想法会是这样的:

//c++ <--> new c++ dll <--> c#
struct CallbackBridge : public Callback
{
    void (*_stateChanged)(int);

    virtual void stateChanged(int state)
    {
        if (_stateChanged)
            _stateChanged(this, state);
    }
};

void* CreateCallback() { return new CallbackBridge(); }
void DeleteCallback(void* callback); { delete callback; }
void setStateChanged(void* callback, void (*ptr)(void*, int))
{
    CallbackBridge* bridge = (CallbackBridge*)callback;
    bridge->stateChanged = ptr;
}
///... other helper methods

这里的想法是将你的对象视为一个黑盒子(因此无处不在 - 它可以是任何指针,但是从c#你只会看到这个aa SafeHandle / IntPtr并编写你可以PInvoke来创建的辅助方法/删除和修改对象。您可以通过这种方法为对象提供委托来模拟这些虚拟调用。

来自c#的用法可能如下所示:(为简单起见,IntPtr,可以使用SafeHandle):

IntPtr callback = CreateCallback();
SetStateChanged(callback, myCallback);
//somewhere later:
DeleteCallback(callback);

void MyCallback(IntrPtr callback, int state)
{
    int someData = SomeOtherHelperMethod(callback);
    ConsoleWrite("SomeData of callback is {0} and it has changed it's state to {1}", someData, state);
}

我知道,对于更大的对象来说这有点笨拙,但是如果没有c ++ / cli包装器,我从来没有找到任何更好的方法来处理虚拟调用等所有棘手的情况。

答案 1 :(得分:2)

您的C#类不能替代C ++结构,它具有完全不同的内部组织。由于虚方法,C ++结构有一个v表。它不再是POD类型。它有一个隐藏字段,其中包含指向v表的指针。以及编译器生成的构造函数和析构函数。 v表是指向虚方法的指针数组。 C#类有类似的东西,称为&#34;方法表&#34;,但它的组织完全不同。例如,它将具有System.Object方法,并包含指向所有方法的指针,而不仅仅是虚拟方法。类对象布局也完全不同。在此上下文中,C ++语言中的 struct 关键字只是 class 关键字的替代,默认情况下所有成员都是公开的。

需要使用C ++ / CLI语言编写的包装类。这种语言有一点学习曲线,但如果您有使用.NET和C ++的经验,那么它并不陡峭。这种包装器的Boilerplate代码位于this answer。您可以通过Marshal :: GetFunctionPointerForDelegate()返回的函数指针从本机代码回调到托管代码。

答案 2 :(得分:0)

我听说你没有C ++库的源代码。然后你可以看一下this

但是将类从非托管dll导出到没有源代码的托管版本对我来说听起来很危险。