Native and Managed Code Interop in the Shared Source CLI 2.0

The Microsoft® Shared Source CLI (SSCLI) 2.0 implementation supports reduced interoperation between native and managed code as compared to the Microsoft .NET Framework implementation on Microsoft® Windows®. Because of platform independence (via the PAL), the SSCLI does not support COM interop in any form.  This includes calling from managed code out to an unmanaged COM implementation as well as native code calling into managed code through COM wrappers over managed classes.

The following tables indicate the interoperability options available on either implementation.

Managed Code Calling Native Code Microsoft .NET Framework Microsoft Shared Source CLI 2.0
COM interop Supported. 

Managed wrapper classes allow managed code to create instances of unmanaged COM classes and invoke methods.

Not supported.
Platform invoke Supported. 

Managed code can call directly to stdcall or cdecl C-style functions exported by dynamic libraries implemented in native code.

Supported. 

No changes from the .NET Frameworks on Windows.

 

Native Code Calling Managed Code Microsoft .NET Framework Microsoft Shared Source CLI 2.0
COM interop Supported. 

Unmanaged code can use COM wrapper classes to create instances of managed classes and invoke methods.

Not supported.
Managed Extensions for C++ Supported. 

Native C++ classes can be compiled in the same assembly as managed classes and can interoperate directly.  Managed methods can be exported directly as dynamic library entry points.

Not supported.
Delegates Supported. 

Managed code implements a delegate that is exported as a function pointer to native code. 

Supported.

No changes from the .NET Framework on Windows.

Foreign Function Interface (FFI) Not supported. Supported. 

This is a light-weight substitute for COM interop.

Foreign Function Interface (FFI)

Because the SSCLI implementation does not support either the Managed Extensions for C++ or COM interop, the Foreign Function Interface (FFI) was implemented.  The FFI allows code implemented in unmanaged native C to create instances of managed classes and invoke methods.  The following FFI class and interface definitions are located in corffi.idl and mscoree.idl.


/* modeled after System.Reflection.BindingFlags */
typedef enum CorFFIBindingFlags
{
  CorFFIInvokeMethod = 0x0100,
  // CreateInstance = 0x0200,
  CorFFIGetField = 0x0400,
  CorFFISetField = 0x0800,
  CorFFIGetProperty = 0x1000,
  CorFFISetProperty = 0x2000,
} CorFFIBindingFlags;

interface IManagedInstanceWrapper : IUnknown { 
  HRESULT InvokeByName( [in] LPCWSTR MemberName, 
			[in] INT32 BindingFlags, 
			[in] INT32 ArgCount, 
			[optional,in,out] VARIANT *ArgList, 
			[optional,out] VARIANT *pRetVal); 
			}

STDAPI ClrCreateManagedInstance(LPCWSTR pTypeName, REFIID riid, void **ppObject);

The SSCLI platform adaptation layer (PAL) runtime supports a limited version of some COM-style types:  rotor_palrt.h defines HRESULT, IUnknown* and VARIANT. Since IManagedInstanceWrapper derives from IUnknown, FFI calls can return managed object references by having the marshaler create a new IManagedInstanceWrapper and store it in the VARIANT as an IUnknown*.   This allows integer types, floating point types, strings, and objects to be passed through FFI in both directions ([in] parameters, [out] parameters, and return values).

FFI is modeled on COM semantics, so some familiarity with COM programming is required to use it.  Standard COM reference-counting semantics must be followed.  For more details, consult standard COM reference documentation.  There are many sources for information on COM programming, for example, The COM Programmer's Cookbook and many other articles on msdn.microsoft.com as well as the Microsoft Visual C++® .NET documentation.

FFI and Object References

Object references are treated as vectors of function pointers. 

FFI and Strings

VT_BST should be used for by-value strings and VT_BSTR | VT_BYREF used for by-reference strings.

FFI and VARIANT

The following VARIANT types can be marshaled:

Note: VT_BYREF can be marshaled but VT_ARRAY is not supported.

FFI Sample Code

    
    IManagedInstanceWrapper *pWrap; 
    VARIANT RetVal; 
    HRESULT hr; 

    hr = ClrCreateManagedInstance( 
        L"System.Random,mscorlib,PublicKeyToken=b03f5f7f11d50a3a", 
        IID_IManagedInstanceWrapper, 
        (void**)&pWrap); 
    if (FAILED(hr)) { 
        fprintf(stderr, "ClrCreateManagedInstance failed with hr=0x%08x\n", hr); 
        return; 
    }   

    VariantClear(&RetVal); 
    hr = pWrap->InvokeByName( 
        L"Next", CorFFIInvokeMethod, 0, NULL, &RetVal); 

    if (FAILED(hr)) { 
        fprintf(stderr, "InvokeMethodByName failed with hr=0x%08x\n", hr); 
        return; 
    } 

    printf("System.Random.Next() returned %d\n", V_I4(&RetVal)); 

    pWrap->Release(); 

SSCLI FFI Implementation Location

The implementation of the SSCLI FFI interop functionality can be found in the following source files:

The following tests show examples of using the FFI functionality:

Wrapping cdecl Native Function Callbacks With Managed Delegates

Both the .NET Framework on Windows and the SSCLI implementation support delegates that can be exposed to native code as either stdcall or cdecl function pointers.  The default calling convention for marshaled delegates is stdcall. Use UnmanagedFunctionPointer attribute on the delegate to override the default:

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal unsafe delegate int Tcl_AppInitProc(Tcl_Interp* interp);

Note that this only impacts cdecl APIs that are callback functions where the native code needs to call back on a function pointer into the managed code for notification purposes. An example of this would be a native API that requires a function pointer to call back a function for progress indication or resource enumeration.

Shared Source CLI Platform Invoke

In general, the SSCLI implementation of a platform invoke method is the same as that provided by the Microsoft .NET Framework on Windows. 

For example,  the SSCLI implementation follows the same rules as those for the .NET Framework for string marshaling. Strings are marshaled by read-only value.  The value that is returned is the pinned string object itself, so you can not modify it because the CLI string data type is immutable.

For some examples of using platform invoke in SSCLI please see %ROTOR_DIR%\tests\dev\interoptest1.cs.

Managed pointers an be marshaled using [MarshalAs(UnmanagedType.IUnknown)].  Because almost any type can be turned into the type Object through boxing, it should be possible to marshal almost all types as IUnknown.   If the default marshaling does not work for your requirements, you can create a custom marshaler in the same way as that supported in the .NET Framework.

Unlike the .NET Framework implementation of a platform invoke method, the SSCLI implementation does not support VARIANT marshalling.
 

Copyright (c) 2006 Microsoft Corporation. All rights reserved.