【问题标题】:Passing a C# class object in and out of a C++ DLL class将 C# 类对象传入和传出 C++ DLL 类
【发布时间】:2012-06-22 20:18:13
【问题描述】:

我一直在开发一个原型代码应用程序,该应用程序在 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);
}

对 DLL 的 C# 函数调用:

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 的指针,所以你可能会得到垃圾。
  • Holder 似乎确实存储了进入 C++ 代码的值以供以后检索。我可以看一下,但我现在主要关心的是从传递给 DLL 的对象中提取一个值。

标签: c# c++ pinvoke marshalling dllimport


【解决方案1】:

如果你只使用 C# 中的类,你应该使用 GCHandle 来处理 http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle%28v=vs.110%29.aspx

编辑:

CClass *Holder;

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

【讨论】:

  • 这很有趣,因为我感觉这与两者之间的内存访问有关,而且我还没有跨过 GCHandle。但是使用它我需要使用 IntPtr 类型来代替传递我的 CSObject。有没有办法将 CSObject 类转换为 IntPtr?否则我无法传递课程内容。谢谢!
  • 您只需将 CClass 替换为 CClass* 或 void* 即可将其用作 C# 端的 IntPtr。你有没有在 C++ 端改变它的值? C# 类 CClass 也自动编组为 CClass*。您可以尝试将您的类声明为结构并使用
  • public static extern void GetDLLObj(out CSObject csObj);
  • 是的,我必须更改 C++ 端的值。当它返回时,我能够获得在 C# 端传入的 1234 值(感谢@Bond),但是我在代码中尝试的任何更改都不会改变输出/返回对象值。我还在 C++ 代码中将对象值显示为 0。
  • 我确实尝试将 C# 类更改为结构,但它产生了一些编组问题。由于某种原因 int intTest2 无法编组到 C++ 对应项。
【解决方案2】:

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

__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 值。这可能与另一个问题有关吗?
  • 我的错误,它必须是对指针的引用或指向指针的指针 - 我修复了我的示例代码。您需要在 getDLLObj 中使用对指针的引用,而只需在 SetDLLObj 中使用指针,并通过在参数上添加 ref 关键字来修复 getDLLObj 的 C# 原型。
  • 感谢 Bond,您的建议有助于删除存储在 DLL 中的垃圾值。但是,我仍然有传入 0 值的问题,它应该是 1234。相信@user629926 提出的未管理内存和托管内存之间仍然存在问题。感谢您的帮助,任何其他想法都会很棒!
  • 究竟什么是垃圾? SetDLLObj(CClass obj) 中的 obj 的 intTest1 字段?你能用你当前的代码更新你的问题吗?
  • 呃,我刚看到这个但是......你不能像这样将托管对象(CSObject)编组为非托管对象(CClass)。它只是行不通,布局不同 - 你的 CClass 从 CObject 继承......啊......我认为这是不可能的,我想你应该为此使用 COM,或者托管 C++(C++/CLI)跨度>
【解决方案3】:

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

我最终只是调用 C++ 函数来创建一个对象并将指针传回 C# 的 IntPtr 类型。然后,我可以将指针从 C# 传递给我需要的任何 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

希望这可以帮助其他人节省比我试图弄清楚更多的时间!

【讨论】:

  • 正是我想要的谢谢,不需要对象,但肯定是对象引用。很好地保持模式的功能
猜你喜欢
  • 1970-01-01
  • 2023-03-08
  • 2015-11-13
  • 1970-01-01
  • 2016-09-03
  • 1970-01-01
  • 1970-01-01
  • 2015-02-01
  • 1970-01-01
相关资源
最近更新 更多