10

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

public struct Part
{
    public double? x;  // or System.Nullable<double> 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 新手)。

我想也许我可以使用不安全的代码,但我不知道如何从双精度转换?加倍*(我也是 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# 还很陌生,所以这对我来说有点困难。

最坏的情况...我将在结构中为每个可为空的类型创建一个布尔成员,并使用它来指示是否将值视为空值。但这似乎很愚蠢。当然,必须有某种方法可以通过 COM 公开可空类型。Microsoft 为什么要创建不能在 COM 中使用的 .NET 类型?雷德蒙德的那些家伙不是白痴(尽管有时看起来确实如此)。

那么,我有哪些选择?做这个的最好方式是什么?

提前致谢。

4

1 回答 1

4

您可以使用 typeobject而不是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;),则返回的类型VARIANTVT_EMPTY,否则为VT_R8.

附带说明一下,向COM 公开类接口不是一个好习惯。您可能希望为此创建一个单独的界面。采用这种方法,您可以将您的界面公开为基于IUnknown,因为您不需要IDispatch为 C++ 管道:

[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)) );
于 2013-08-26T02:21:03.900 回答