如何通过COM公开可为空的类型

10

我已经为此问题苦苦挣扎了一天半,希望有人能帮助我。假设我在C#中有以下这样的结构:

public struct Part
{
    public double? x;  // or System.Nullable<double> x, doesn't really matter
}

(这些结构代表数据库表,是由SQLMetal创建的Linq to 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?转换为double *(我对C#也很陌生!)。
unsafe public struct Part
{
    public double* x;
}

Part part = new Part()
{
    x = AnotherObject.x // AnotherObject.x is a System.Nullable<double>
}

我原本以为可以使用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公开可空类型。为什么微软会创建.NET类型,不能在COM中使用呢?那些红蒙城的家伙不是傻瓜(尽管有时候看起来确实是这样)。

那么,我的选择是什么?最好的方法是什么?

提前感谢您的帮助。


2
COM 仅公开接口,而不是类。也没有字段,只有属性。变量类型可以使用 variant,但在 C# 代码中必须声明为 object - Hans Passant
好的,汉斯...我该如何使用Object?如果我只是将类型更改为Object:`public struct Part { public Object x; }`那么我最终会在类型库中得到一个LPUNKNOWN:`struct Part { LPUNKNOWN x; }`我是否只需在C++代码中将LPUNKNOWN强制转换为VARIANT,还是我做错了什么? - Dennis Jones
并且使用 dynamic 也可能有所帮助。例如,http://stackoverflow.com/questions/12118077/using-javascript-for-custom-purposes - I4V
我很感激您的帮助,但我真的需要一些示例。请记住,我对C#和COM都很陌生。 - Dennis Jones
1
我需要支持旧版本的Borland C++Builder。最终我采用了不同的方法:使用一个简单的结构体来表示可空类型——它非常有效。 - Dennis Jones
显示剩余5条评论
1个回答

4
你可以使用 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

如果将字段设置为nulldouble? testValue = null;),则返回的VARIANTtypeVT_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)) );

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