C# 中 C++ 的 VARIANT 数据类型等价于什么?
我有使用 VARIANT 数据类型的 C++ 代码。如何在 C# 中转换该代码?
C# 中 C++ 的 VARIANT 数据类型等价于什么?
我有使用 VARIANT 数据类型的 C++ 代码。如何在 C# 中转换该代码?
嗯,C++ 中实际上有两个变体:boost::variant 和 COM 变体。解决方案或多或少遵循相同的想法,但前者更复杂。我希望您的意思是使用后者。
首先让我告诉你,如果可能的话,这是你不应该使用的东西。也就是说,这就是你的做法:-)
变体和互操作
如果您需要相同的字节表示,有时会在互操作中使用变体。
如果您正在处理互操作,请务必查看VariantWrapper
MSDN 上的课程并使其像那样工作。
变体和移植注意事项
变体主要用于 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
.
这是一个棘手的问题。
从 C# 4 开始,您可以使用dynamic来指示该类型在运行时是已知的。
但是,根据我个人的理解,c++ 需要在编译时已知的类型。因此,您可能会考虑使用object
,但object
在 C# 中是一种存在的类型。
对于 VARIANT 的多类型、单值(AKA 多态性)的概念,您不需要在 C# 中找到相应的类型,只需定义您的类和接口即可。您始终可以将对象作为类实现的接口来引用。
如果您要移植代码,并且要找出可以在 LHS 中简单使用的语法并且考虑到在编译时已知的类型,请使用var。
当.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 实现而不在某处释放它...
对于更复杂的代码,您可以阅读 这篇文章,其中还讨论了内存管理。
让我们退后一步。迟早,我们想要 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。