简体   繁体   中英

How to expose nullable types via COM

I have been struggling with this problem for a day and a half, hopefully someone can help me. Let's say I have structures like this in C#:

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

(these structures represent database tables, converted from Linq to SQL code created by SQLMetal)

I need to be able to expose these structures, containing nullable types, to COM so that they can be used in another application (C++). But I cannot figure out, for the life of me, how to do this. I thought I could create classes to encapsulate the nullable types:

public class NullableDouble
{
    private double _Value;
    // class methods...
}

public struct Part
{
    public NullableDouble x;
}

That sort of works, but on the C++ side, I end up with a pointer to a class but no class definition (just an interface):

interface DECLSPEC_UUID("{E8EE4597-825D-3F4C-B20B-FD6E9026A51C}") _NullableDouble;

struct Part
{
    MyDll_tlb::_NullableDouble* x;
}

Thus, I cannot dereference that pointer without a class definition from which to access the data members/methods on the C++ side. This still seems like a good option, if I can just figure out how to get the class definition in C++ (I'm new to COM).

I thought maybe I could use unsafe code, but I can't figure out how to convert from double? to double* (I'm new to C# too!):

unsafe public struct Part
{
    public double* x;
}

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

I thought maybe I could use System.Variant, but C# doesn't like that either (inconsistent accessibility, whatever that means).

public struct Part
{
    public Variant x;  
    // produces 2 errors: 
    //   1) System.Variant inaccessible, 
    //   2) inconsistent accessibility
}

I've been using C++ for 20 years, but I am fairly new to COM and C#, so this is a bit of a struggle for me.

Worst-case scenario...I'll just create a boolean member in the struct for each of the nullable types and use that to indicate whether the value is to be treated as if it is null. But that just seems stupid. Surely there must be some way to expose nullable types via COM. Why would Microsoft create .NET types that can't be used in COM? Those guys in Redmond aren't idiots (although sometimes it sure seems like it).

So, what are my options? What is the best way to do this?

Thanks in advance.

You could use type object instead of double? for your structure field and apply [MarshalAs(UnmanagedType.Struct)] to it to marshal it as VARIANT . Here's an excellent article on the subject .

Code sample:

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;
        }
    }
}

Register (for a 32-bit assembly):

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;
}

Output:

Value: 1.100000

If the field is set to null ( double? testValue = null; ), the type of the returned VARIANT will be VT_EMPTY , otherwise it's VT_R8 .

On a side note, it is not a good practice to expose a class interface to COM. You may want to create a separate interface for that. Taking this approach, you can expose your interface as based on IUnknown , because you don't need IDispatch plumbing for 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)) );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM