Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 571
Dennis Jones Avatar asked Aug 24 '13 17:08

Dennis Jones


People also ask

How do you access the underlying value of a nullable type?

You cannot directly access the value of the Nullable type. You have to use GetValueOrDefault() method to get an original assigned value if it is not null. You will get the default value if it is null. The default value for null will be zero.

How are nullable types declared in C#?

You can declare nullable types using Nullable<t> where T is a type. Nullable<int> i = null; A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. For example, Nullable<int> can be assigned any value from -2147483648 to 2147483647, or a null value.

How do you handle nullable values in C#?

IsNullOrEmpty() Method of C# This method is used when you want to check whether the given string is Empty or have a null value or not? If any string is not assigned any value, then it will have Null value. The symbol of assigning Null value is “ “or String. Empty(A constant for empty strings).

Are nullable types reference types in C#?

Nullable and non-nullable reference types C# version 8.0 allows us to pre-set properties for reference types. Accordingly, a reference type can either be nullable or non-nullable.


1 Answers

You could use type objectinstead 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)) );
like image 60
noseratio Avatar answered Oct 06 '22 22:10

noseratio