使用 C# P/Invoke 通过方法封送结构

发布于 2024-12-21 16:09:58 字数 656 浏览 1 评论 0原文

我正在尝试 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);

I'm trying to PInvoke into this C++ library function:

int Initialize(Callback* callback);

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

I have tried this naive C# code, but it doesn't work.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

屋檐 2024-12-28 16:09:58

据我所知,这样做是不可能的。方法并不像字段那样“在那里”,除此之外,使用虚方法创建结构会在对象中创建 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

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

。 (为了简单起见,可以使用 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 包装器,我从来没有找到任何更好的方法来处理所有这些棘手的情况,例如虚拟调用等。

It is impossible to do it that way as far as I know. Methods are not "in there" as fields would be, beside this, creating a struct with virtual method will create a vtable pointer in your objects, that you are not taking into account in c# mirrored class.

What you can do is to PInvoke to method that takes a functionPointer (in C++) and pass a delegate (C#) there. You can then use this function pointer to call it from native code and your delegate will launch.

You could then change your stateChange method definition to take a Callback* as a first parameter, so when you call it from native code you can pass an object pointer which is responsible of that change and marshal it back to Callback in c#.

//Edit without having source of native dll, building a bridge between c# and c++ is what comes to my mind. This can be done with c++/cli or c++, sticking to native my idea would be something like this:

//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

The idea here is to treat your object as a black box (hence void* everywhere - it can be any pointer, but from c# you will just see this a a SafeHandle / IntPtr and write helper methods that you can PInvoke to to create / delete and modify objects. You can mock those virtual calls by giving your object a delegate through such method.

From c# usage could look like this: (IntPtr for simplicity, SafeHandle could be used):

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

I know, it's a bit clumsy for bigger objects, but without c++/cli wrapper I have never found any better way to be able to handle all those tricky cases like virtual calls etc.

迷离° 2024-12-28 16:09:58

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

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

Your C# class is no substitute for the C++ struct, it has an entirely different internal organization. The C++ struct has a v-table because of the virtual method. It is not a POD type anymore. It has a hidden field that contains a pointer to the v-table. And a compiler-generated constructor and destructor. The v-table is an array of pointers to the virtual methods. A C# class has something similar, called "method table", but it organized completely differently. It will have the System.Object methods for example and contains pointers to all methods, not just the virtual ones. The class object layout is completely different as well. The struct keyword in the C++ language in this context is just a substitute for the class keyword with all members public by default.

A wrapper class written in the C++/CLI language is required. There's a bit of a learning curve to the language but it is not steep if you've got experience with .NET and C++. Boilerplate code for such a wrapper is in this answer. You can call back from native code into managed code through a function pointer returned by Marshal::GetFunctionPointerForDelegate().

撩动你心 2024-12-28 16:09:58

我听说你们没有 C++ 库的源代码。那么您可以看看这个

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

I have heard that you don't have the source code of C++ library. Then you might have a look at this.

But exporting classes from unmanaged dll to managed one without source sounds dangerous to me.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文