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 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.
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.
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).
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.
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)) );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With