10

C# 中 C++ 的 VARIANT 数据类型等价于什么?

我有使用 VARIANT 数据类型的 C++ 代码。如何在 C# 中转换该代码?

4

4 回答 4

9

嗯,C++ 中实际上有两个变体:boost::variant 和 COM 变体。解决方案或多或少遵循相同的想法,但前者更复杂。我希望您的意思是使用后者。

首先让我告诉你,如果可能的话,这是你不应该使用的东西。也就是说,这就是你的做法:-)

变体和互操作

如果您需要相同的字节表示,有时会在互操作中使用变体。

如果您正在处理互操作,请务必查看VariantWrapperMSDN 上的课程并使其像那样工作。

变体和移植注意事项

变体主要用于 API,通常是这样的:

void Foo(SomeEnum operation, Variant data);

在 C++ 中这样做的原因是因为没有基object类,因此您需要这样的东西。最简单的移植方法是将签名更改为:

void Foo(SomeEnum operation, object data);

但是,如果您仍然要移植,您还需要认真考虑这两个,因为它们在编译时就已解决,并且可以为您节省通常在方法中遵循的大“开关” Foo

void SomeOperation(int data);
void SomeOperation(float data);
// etc

变体和字节一致性

在极少数情况下,您需要自己操作字节。

本质上,变体只是包装在单个值类型(结构)中的值类型的大联合。在 C++ 中,您可以在堆上分配值类型,因为结构与类相同(很好排序)。如何使用值类型有点重要,但稍后会详细介绍。

联合只是意味着您将重叠内存中的所有数据。请注意我是如何在上面明确指出值类型的;对于变体,这基本上就是它的全部内容。这也为我们提供了一种测试它的方法——即通过检查结构中的另一个值。

在 C# 中做到这一点的方法是StructLayout在值类型中使用属性,其工作原理基本上如下:

[StructLayout(LayoutKind.Explicit)]
public struct Variant
{
    [FieldOffset(0)]
    public int Integer;
    [FieldOffset(0)]
    public float Float;
    [FieldOffset(0)]
    public double Double;
    [FieldOffset(0)]
    public byte Byte;
    // etc
}

// Check if it works - shouldn't print 0.
public class VariantTest
{
    static void Main(string[] args)
    {
        Variant v = new Variant() { Integer = 2 };
        Console.WriteLine("{0}", v.Float);

        Console.ReadLine();
    }
}

如前所述,C++ 变体也可以存储在堆上。如果这样做,您可能仍希望内存签名保持不变。做到这一点的方法是将我们之前构建的 Variant 结构装箱,只需将其封装为object.

于 2013-04-11T11:51:16.897 回答
1

这是一个棘手的问题。

从 C# 4 开始,您可以使用dynamic来指示该类型在运行时是已知的。

但是,根据我个人的理解,c++ 需要在编译时已知的类型。因此,您可能会考虑使用object,但object在 C# 中是一种存在的类型。

对于 VARIANT 的多类型、单值(AKA 多态性)的概念,您不需要在 C# 中找到相应的类型,只需定义您的类和接口即可。您始终可以将对象作为类实现的接口来引用。

如果您要移植代码,并且要找出可以在 LHS 中简单使用的语法并且考虑到在编译时已知的类型,请使用var

于 2013-04-04T12:10:38.010 回答
1

.NET 实现 COM 接口时,只需使用 VARIANT*代替。

然后使用IntPtr类型绕过 .NET 接收端的编组以接收指针。

public class ComVariant
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Variant
    {
        public ushort vt;
        public ushort wReserved1;
        public ushort wReserved2;
        public ushort wReserved3;
        public Int32 data01;
        public Int32 data02;
    }

    private Variant _variant;

    private IntPtr _variantPtr;

    public ComVariant(int variantPtr) : this(new IntPtr(variantPtr))
    {
    }

    public ComVariant(IntPtr variantPtr)
    {
        _variant = (Variant)Marshal.PtrToStructure(variantPtr, typeof(Variant));
        _variantPtr = variantPtr;
    }

    public VarEnum Vt
    {
        get
        {
            return (VarEnum)_variant.vt;
        }
        set
        {
            _variant.vt = (ushort)value;
        }
    }

    public object Object
    {
        get
        {
            return Marshal.GetObjectForNativeVariant(_variantPtr);
        }
    }
}

那么如果您正在访问指向 COM 接口对象实例的 VT_UNKNOWN,只需

var variant = new ComVariant(variantPtr);
var stream = variant.Object as IStream; // will not be null if type is correct
var obj = variant.Object as IObj; // in general...

可以解决问题,但请注意不要使用新分配的 VARIANT 并将其所有权授予 .NET 实现而不在某处释放它...

对于更复杂的代码,您可以阅读 这篇文章,其中还讨论了内存管理。

于 2014-10-16T18:20:02.703 回答
0

让我们退后一步。迟早,我们想要 VARIANT 中的实际数据。VARIANT 只是有意义数据的持有者。假设我们将 VARIANT 转换为 C# 中的某种对象,该对象具有变体类型和 .NET 引擎盖下的一些原始缓冲区(例如,.NET 字符串可以公开原始缓冲区)。此时,需要根据对象确定 VARIANT 类型,并将原始数据转换或强制转换为变体指定的数据类型,然后创建一个新对象,例如 string/int/etc。从原始数据。

因此,不必担心将 VARIANT 传递给 C#,而是查看变量数据类型并将其在 C++ 中转换为实际数据类型并将其传递给 C#。

例如,如果 VARIANT 类型是 VT_INT,则从变体中获取 int 并可以使用如下内容:

VARIANT var;

Int^ returnInt = gcnew Int(var.intVal);

returnInt 可以作为 Out 参数从可以从 C# 调用的 C++ dll 中的 C++ 函数返回。C++ dll 需要使用 /clr 选项。

功能看起来像: -

void ThisFunctionReturnsAnInt(Runtime::InteropServices::OutAttribute Int^ % returnIntValue)
{

    VARIANT var;
    Int^ returnInt = gcnew Int(var.intVal);
}

可以对其他数据类型使用类似的方法。这是很自然的,VT_INT 的 VARIANT 真的就像一个 int,这并不是说正在进行一些重大的转换,你只是在你对它感兴趣的时候从 VARIANT 中取出实际值如果您将直接整数值从 C++ 传递到 C#。无论如何,您仍然需要执行 gcnew。

于 2013-06-29T07:24:49.723 回答