locked
Marshalling VARIANT across ABI boundaries in a WinRT component

    Question

  • Hi,

    I am porting our component which is written in C++ at its core and has both an ActiveX and a .Net shell. The component internally uses the VARIANT type in many places. Some public properties (get/set) and methods of this component's arguments are of the VARIANT type in the ActiveX implementation and System::Object in the .Net implementation. Internally in our code we use the VARIANT directly.

    When implementing the ActiveX component, I did not need to do any marshaling since VARIANT is an OLE/COM type.

    When implementing .Net component, I used similar to this:

    VARIANT var;
    //...
    //Initialize the VARIANT value
    //...
    System::IntPtr p( &var );
    System::Object ^o = System::Runtime::InteropServices::Marshal::GetObjectForNativeVariant( p );
    return o;

    In WinRT, there does not seem to be any similar Marshal class that will do the job. According to MSDN "The WinRT Platform::Runtime::InteropServices namespace is intended for internal use only, and is not intended to be used for development."

    What are my options? Surely there must be an existing class to do the work of marshalling a VARIANT across ABI boundaries. I don't want to write such a marshaller and then find out that it already exists.

    Any help would be much appreciated.
    Regards,
    Roger
    Monday, August 13, 2012 2:31 PM

Answers

  • In WinRT, the closest to VARIANT is the Windows::Foundation::IPropertyValue. You can use boxing in C++/CX and C# to pass IPropertyValue instances around. In C++/CX, you would write:

    Platform::Object^ GetSomeValue()
    {
    	bool b = false;
    	int i = 10;
    	float f = 10.0f;
    	if (b)
    		return i;
    	else
    		return f;
    }
     

    Or to be extra-explicit about the implicit conversions done by the compiler automatically:

    if (b)
    	return ref new Platform::Box<int>(i);//i;
    else
    	return ref new Platform::Box<float>(f);//f;

    Hope this helps,
    Marian Luparu
    Visual C++

    • Proposed as answer by Jesse Jiang Wednesday, August 15, 2012 5:39 AM
    • Marked as answer by Jesse Jiang Monday, August 20, 2012 7:39 AM
    Monday, August 13, 2012 11:03 PM

All replies

  • In WinRT, the closest to VARIANT is the Windows::Foundation::IPropertyValue. You can use boxing in C++/CX and C# to pass IPropertyValue instances around. In C++/CX, you would write:

    Platform::Object^ GetSomeValue()
    {
    	bool b = false;
    	int i = 10;
    	float f = 10.0f;
    	if (b)
    		return i;
    	else
    		return f;
    }
     

    Or to be extra-explicit about the implicit conversions done by the compiler automatically:

    if (b)
    	return ref new Platform::Box<int>(i);//i;
    else
    	return ref new Platform::Box<float>(f);//f;

    Hope this helps,
    Marian Luparu
    Visual C++

    • Proposed as answer by Jesse Jiang Wednesday, August 15, 2012 5:39 AM
    • Marked as answer by Jesse Jiang Monday, August 20, 2012 7:39 AM
    Monday, August 13, 2012 11:03 PM
  • Hi Marian,

    Thanks for your reply. It was helpful and I ended up writing a utility class with your suggestion about using Windows::Foundation::IPropertyValue.

    I posted the code here together with a link back to this thread:

    http://www.amyuni.com/forum/viewtopic.php?f=24&t=2854

    Example:
    // in a C# we can write:
    string [] sarray = new string[2];
    sarray[0] = "abc";
    sarray[1] = "def";

    SetValue(sarray);

    //in C++/CX we write:
    void SetValue( Object ^value )
    {
        VARIANT var;
        VariantInit( &var );

        var = acMarshall::MarshalObjectToVariant( value );
        //
        // We will have a SAFEARRAY of BSTRs in the VARIANT...
        //
        VariantClear( &var );
    }

    // and the reverse, in C++/CX
    Object ^ GetValue()
    {
        VARIANT var;
        VariantInit(&var);
        
        v.vt = VT_BSTR | VT_ARRAY;
        
        SAFEARRAYBOUND sab;
        sab.cElements = 2;
        sab.lLbound = 0;
        SAFEARRAY *psa = SafeArrayCreate( VT_BSTR, 1, &sab );
        LPVOID p = NULL;
        SafeArrayAccessData( psa, &p );
        
        ((BSTR *) p)[0] = SysAllocString(L"string one");
        ((BSTR *) p)[1] = SysAllocString(L"string two");
        
        SafeArrayUnaccessData( psa );
        var.parray = psa;
        
        Object ^ obj = acMarshall::MarshalVariantToObject( var );
        VariantClear( &var );
        
        return obj;
    }

    //and in C#:
    object obj = GetValue() //obj will contain a string array...

    Regards,

    Roger

    • Proposed as answer by yms_2009 Wednesday, August 22, 2012 3:31 PM
    Monday, August 20, 2012 4:36 PM
  • Good start. I recommend keeping an eye on error handling in your implementation.

    C++/CX is exception based and the VARIANT APIs is HRESULT-based. For an example of how to do the error handling in these mixed scenarios, take a look at the Direct3D project template shipping in VS2012. E.g.:

    acMarshal::ThrowIfFailed(SafeArrayLock(...

    rather than:

    if (SUCCEEDED(hr = SafeArrayLock(...

    Marian

    Wednesday, August 22, 2012 6:23 PM