10

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.

Drew
  • 29,895
  • 7
  • 74
  • 104
Dennis Jones
  • 211
  • 2
  • 13
  • 2
    COM does not expose classes, only interfaces. Nor fields, only properties. A variant is okayish, you'll have to declare it *object* in your C# code. – Hans Passant Aug 24 '13 at 17:26
  • Okay, Hans...how do I use Object? If I just change the type to Object: `public struct Part { public Object x; }` then I end up with an LPUNKNOWN in the type library: `struct Part { LPUNKNOWN x; }` Do I simply cast LPUNKNOWN to VARIANT in the C++ code, or did I do something wrong? – Dennis Jones Aug 24 '13 at 17:38
  • and using `dynamic` may help too. For ex, http://stackoverflow.com/questions/12118077/using-javascript-for-custom-purposes – I4V Aug 24 '13 at 17:39
  • As I mentioned, use a property. Best way to keep out of trouble is to actually declare the interface in your C# code. That stops you from using declarations that don't work in COM. – Hans Passant Aug 24 '13 at 17:40
  • `dynamic` also results in an LPUNKNOWN (same as `Object`) in the type library code. How do I use it? – Dennis Jones Aug 24 '13 at 17:43
  • I appreciate the help, but I could really use some examples. Remember, I'm new to both C# and COM. – Dennis Jones Aug 24 '13 at 17:44
  • @user2713935, I've posted a sample using `object` as the field type. I've also requested an edit to your question to add [visual-c++] tag. – noseratio Aug 26 '13 at 03:29
  • I didn't tag it with visual-c++ because I am not using visual-c++! – Dennis Jones Aug 26 '13 at 13:51
  • What C++ compiler are you using? Did `MarshalAs(UnmanagedType.Struct)` solve your problem anyway? – noseratio Aug 26 '13 at 17:48
  • 1
    I'm having to support an old version of Borland C++Builder. The solution I ended up with took a different approach: a simple struct to represent the nullable types -- it worked beautifully. – Dennis Jones Sep 02 '13 at 14:29

1 Answers1

4

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)) );
noseratio
  • 59,932
  • 34
  • 208
  • 486