将C#类对象传入和传出C++ DLL类

16

我一直在开发一个原型代码应用程序,它运行在C#中并使用来自旧的C++代码的类和函数(以导入的DLL形式)。代码要求是将类对象传递给非托管的C++ DLL(从C#中),并且由C#应用程序存储/修改以便稍后检索。这是我到目前为止编写的代码...

简单的C++ DLL类:

class CClass : public CObject
{
public:
    int intTest1
};

C++ DLL函数:

CClass *Holder = new CClass;

extern "C"
{
    // obj always comes in with a 0 value.
    __declspec(dllexport) void SetDLLObj(CClass* obj)
    {
        Holder = obj;
    }

    // obj should leave with value of Holder (from SetDLLObj).
    __declspec(dllexport) void GetDLLObj(__out CClass* &obj)
    {
        obj = Holder;
    }
}

C#类和包装器:

[StructureLayout(LayoutKind.Sequential)]
public class CSObject
{
    public int intTest2;
}

class LibWrapper
{
    [DLLImport("CPPDLL.dll")]
    public static extern void SetDLLObj([MarshalAs(UnmanagedType.LPStruct)] 
      CSObject csObj);
    public static extern void GetDLLObj([MarshalAs(UnmanagedType.LPStruct)] 
      ref CSObject csObj);
}

C#调用DLL的函数:

class TestCall
{
    public static void CallDLL()
    {
        ...
        CSObject objIn = new CSObject();
        objIn.intTest2 = 1234; // Just so it contains something.
        LibWrapper.SetDLLObj(objIn);
        CSObject objOut = new CSObject();
        LibWrapper.GetDLLObj(ref objOut);
        MessageBox.Show(objOut.intTest2.ToString()); // This only outputs "0".
        ...
    }
}

在 DLL 中(来自传入的 C# 对象),似乎只有垃圾值是可用的。我认为我在类交换或内存/指针问题方面遗漏了什么。我错过了什么?

编辑: 我更改了上面的代码以反映由 Bond 建议的 C#/C++ 方法/函数定义的更改。现在,通过 C# 代码正确地检索到了传递的值(1234)。这暴露出 C++ DLL 中的另一个问题。1234 值在 C++ 代码中不可用。相反,在 DLL 内部,该对象的值为 0。我想使用预定义的 C++ 函数在 DLL 中编辑对象。非常感谢任何更多的帮助。谢谢!


__declspec(dllexport) void SetDLLObj(CClass obj) { Holder = &obj; }这段代码会将本地CClass的指针存储起来,因此可能会导致垃圾数据。 - user629926
Holder 似乎正在存储进入 C++ 代码的值以供稍后检索。我可以查看它,但我现在主要关注从传递到 DLL 中的对象中提取值。 - notsodev
3个回答

12

邦德是正确的,我无法在受管和非受管代码之间传递对象并仍然保留其存储信息。

最终,我只调用了C++函数来创建一个对象,并将指针传回到C#的IntPtr类型中。我可以将指针传递给任何需要的C++函数(只要它是extern)从C#中使用。这并不完全符合我们想做的事情,但它将为我们所需的范围提供服务。

这是我正在使用的C#包装器的示例/参考。(注意:我使用StringBuilder而不是上面示例中的'int intTest'。 这是我们原型所需的。 我只是在类对象中使用整数来简化。):

class LibWrapper
{
    [DllImport("CPPDLL.dll")]
    public static extern IntPtr CreateObject();
    [DllImport("CPPDLL.dll")]
    public static extern void SetObjectData(IntPtr ptrObj, StringBuilder strInput);
    [DllImport("CPPDLL.dll")]
    public static extern StringBuilder GetObjectData(IntPtr ptrObj);
    [DllImport("CPPDLL.dll")]
    public static extern void DisposeObject(IntPtr ptrObj);
}

public static void CallDLL()
{
    try
    {
        IntPtr ptrObj = Marshal.AllocHGlobal(4);
        ptrObj = LibWrapper.CreateObject();
        StringBuilder strInput = new StringBuilder();
        strInput.Append("DLL Test");
        MessageBox.Show("Before DLL Call: " + strInput.ToString());
        LibWrapper.SetObjectData(ptrObj, strInput);
        StringBuilder strOutput = new StringBuilder();
        strOutput = LibWrapper.GetObjectData(ptrObj);
        MessageBox.Show("After DLL Call: " + strOutput.ToString());
        LibWrapper.DisposeObject(ptrObj);
    }
    ...
}
当然,C++执行所有需要的修改,C#访问内容的唯一途径就是通过C++请求所需的内容。以这种方式,C#代码无法访问未管理的类中的内容,因此在两端编写代码会更加冗长,但它对我很有效。
这些是我用来构建解决方案基础的参考资料: http://www.codeproject.com/Articles/18032/How-to-Marshal-a-C-Class 希望这可以帮助其他人比我更省时间地解决问题!

正是我所需要的,谢谢。不需要对象,但对象引用肯定需要。保持模式的功能性很好。 - Jamie Nicholl-Shelley

5
我认为你应该像这样声明你的返回方法:

我相信您应该这样声明您的返回方法

__declspec(dllexport) void getDLLObj(__out CClass* &obj)

以及相应的C#原型

public static extern void GetDLLObj([MarshalAs(UnmanagedType.LPStruct)] 
      ref CSObject csObj);

inbound方法也应该接受CClass指针,C#原型是正确的。


当我尝试编译DLL时,出现“指向引用非法”的错误。此外,将C++函数定义为引用会导致公共类中的“无法访问私有成员”错误。如果将返回函数定义为指向类的指针,则确实会更改C#应用程序中的输出,但是我提到的类垃圾传递到DL中时,其值更改为0。这可能与另一个问题有关吗? - notsodev
我的错误,它必须是指针的引用或指向指针的指针 - 我已经修复了我的示例代码。在getDLLObj中,您需要使用指针的引用,在SetDLLObj中只需使用指针,并通过在参数上添加ref关键字来修复getDLLObj的C#原型。 - Ventsyslav Raikov
谢谢Bond,你的建议帮助我删除了存储在DLL中的垃圾值。然而,我仍然有一个问题,就是传递了一个0值,而应该是1234。像@user629926提出的那样,我相信仍然存在未管理和托管内存之间的问题。感谢你的帮助,如果有其他想法,那就太好了! - notsodev
什么是“junk”?在SetDLLObj(CClass obj)中,obj的intTest1字段是什么意思?您能否更新您的问题并提供当前的代码? - Ventsyslav Raikov
我所指的垃圾是来自对象指针的随机int值。更改代码消除了垃圾值,但在C++代码中将其替换为intTest1值为0。如果我没错的话,这意味着接收到了一个空的CClass对象?指针不再指向随机值。我已编辑上面的代码以反映更改。谢谢! - notsodev
2
哦,我刚看到这个问题...你不能像这样将托管对象(CSObject)直接转换为非托管对象(CClass)。这是行不通的,因为它们的布局不同 - 你的CClass继承自CObject...啊...我认为这是不可能的,我想你应该使用COM或者托管C++(C++/CLI)来解决这个问题。 - Ventsyslav Raikov

0

这很有趣,因为我感觉这与两者之间的内存访问有关,而我还没有遇到过GCHandle。但是使用它,我需要在传递我的CSObject时使用IntPtr类型。是否有一种方法可以将CSObject类转换为IntPtr?否则,我无法传递类内容。谢谢! - notsodev
你只需要把 CClass 替换为 CClass* 或 void,就可以在C#端使用它作为 IntPtr。你需要在 C++ 端更改它的值吗?此外,C# 类 CClass 会自动驱动为CClass。你可以尝试将你的类声明为结构体并使用它。 - user629926
公共的静态外部方法 GetDLLObj(out CSObject csObj); - user629926
是的,我确实需要在C++端更改值。当1234值传递到C#端时(感谢@Bond),我能够获取它,但是任何我尝试在代码中进行的更改都不会改变输出/返回对象的值。在C++代码中,我仍然显示对象的值为0。 - notsodev
我尝试将 C# 类更改为结构体,但是出现了一些 marshalling 问题。由于某种原因,int intTest2 无法转换为 C++ 对应项。 - notsodev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接