如何通过COM公开可空类型
我一直在努力解决这个问题一天半,希望有人可以帮助我。 假设我在C#中有这样的结构:
public struct Part { public double? x; // or System.Nullable x, doesn't really matter }
(这些结构表示数据库表,从Linq转换为SQLMetal创建的SQL代码)
我需要能够将包含可空类型的这些结构公开给COM,以便它们可以在另一个应用程序(C ++)中使用。 但我无法弄清楚,对于我的生活,如何做到这一点。 我以为我可以创建类来封装可空类型:
public class NullableDouble { private double _Value; // class methods... } public struct Part { public NullableDouble x; }
这种工作,但在C ++方面,我最终得到一个指向类的指针,但没有类定义(只是一个接口):
interface DECLSPEC_UUID("{E8EE4597-825D-3F4C-B20B-FD6E9026A51C}") _NullableDouble; struct Part { MyDll_tlb::_NullableDouble* x; }
因此,我无法取消引用没有类定义的指针,从中可以访问C ++端的数据成员/方法。 如果我能弄清楚如何在C ++中获取类定义(我是COM的新手),这似乎仍然是一个不错的选择。
我想也许我可以使用不安全的代码,但我无法弄清楚如何从double转换? 加倍*(我也是C#的新手!):
unsafe public struct Part { public double* x; } Part part = new Part() { x = AnotherObject.x // AnotherObject.x is a System.Nullable }
我想也许我可以使用System.Variant,但C#也不喜欢它(不一致的可访问性,无论这意味着什么)。
public struct Part { public Variant x; // produces 2 errors: // 1) System.Variant inaccessible, // 2) inconsistent accessibility }
我已经使用C ++ 20年了,但我对COM和C#还是比较陌生的,所以这对我来说有点挣扎。
最糟糕的情况……我将在结构中为每个可空类型创建一个布尔成员,并使用它来指示该值是否被视为null。 但这似乎很愚蠢。 当然必须有一些方法通过COM公开可空类型。 为什么Microsoft会创建无法在COM中使用的.NET类型? 雷德蒙德的那些家伙不是白痴(尽管有时肯定会这样)。
那么,我的选择是什么? 做这个的最好方式是什么?
提前致谢。
你可以使用type object
而不是double?
对于您的结构字段并将[MarshalAs(UnmanagedType.Struct)]
应用于它以将其编组为VARIANT
。 这是一篇关于这个主题的优秀文章 。
代码示例:
C#:
using System.Runtime.InteropServices; namespace InteropTest { [ComVisible(true)] public struct TestStruct { [MarshalAs(UnmanagedType.Struct)] public object testField; } [ComVisible(true)] [ClassInterface(ClassInterfaceType.AutoDual)] [Guid("6E0DD830-1BF9-41E0-BBEB-4CC314BBCB55")] public class TestClass { public void GetTestStruct(ref TestStruct p) { double? testValue = 1.1; p.testField = testValue; } } }
注册 (用于32位汇编):
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /codebase /tlb InteropTest.dll
C ++:
#include "stdafx.h" #import "InteropTest.tlb" raw_interfaces_only #define _S(a) \ { HRESULT hr = (a); if (FAILED(hr)) return hr; } int _tmain(int argc, _TCHAR* argv[]) { _S( CoInitialize(NULL) ) InteropTest::_TestClassPtr testClass; _S( testClass.CreateInstance(__uuidof(InteropTest::TestClass)) ); InteropTest::TestStruct s; VariantInit(&s.testField); _S( testClass->GetTestStruct(&s) ); printf("Value: %f", V_R8(&s.testField)); CoUninitialize(); return 0; }
输出:
Value: 1.100000
如果该字段设置为null
( double? testValue = null;
),则返回的VARIANT
将为VT_EMPTY
,否则为VT_R8
。
另外,将类接口公开给COM 并不是一个好习惯 。 您可能想为此创建单独的界面。 采用这种方法,您可以基于IUnknown
公开您的界面,因为您不需要C ++的IDispatch
管道:
[ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("E3B77594-8168-4C12-9041-9A7D3FE4035F")] public interface ITestClass { void GetTestStruct(ref TestStruct p); } [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] [ComDefaultInterface(typeof(ITestClass))] [Guid("6E0DD830-1BF9-41E0-BBEB-4CC314BBCB55")] public class TestClass : ITestClass { public void GetTestStruct(ref TestStruct p) { double? testValue = 1.1; p.testField = testValue; } }
C ++:
InteropTest::ITestClassPtr testClass; _S( testClass.CreateInstance(__uuidof(InteropTest::TestClass)) );