Topics covered:

  • Comparing “managed execution” and "native execution”
  • The metadata-driven partnership
  • Mechanics of managed/native interop
  • CLR => Win32
    • P/Invoke
  • CLR => COM

Video (in Bulgarian)

Presentation Content

Comparing “Managed” & “Native” Execution

  • In practice, .NET apps involve a mix of managed & native code
    • CLR-based "managed code”
    • Win32/COM-based “native code”
    • Transitions between the two worlds must be carefully coordinated
      • It’s more than simply parameter marshaling
  • Process Address Space
“Managed” Execution “Native” Execution
IL Validation & Verification Pointer Arithmetic & Arbitrary Code Exec
Code Access Security Process-Centric Security
Garbage Collection Manual Memory Management
Managed Exception Handling Structured Exception Handling

The Metadata-Driven Partnership

  • Metadata (type information) drives managed/native interop
    • Managed type info can be derived from native type info
    • Native type info can be derived from managed type info
    • Pro: tools can automate most (not all) transformations
    • Con: tools can automate most ( not all ) transformations
  • lnterop is a partnership involving you, the compiler, and the CLR
    • The CLR (and it tools) will automate as much as possible
      • limited only by the fidelity of the native type information that’s available
    • You may have to fill in some blanks or fine tune some transformations

Interop Facilities

  • The CLR supports two kinds of managed/native interop
  • lnterop with Win32 DLLs
  • lnterop with COM components
    • “COM Interop”

P/Invoke

  • Managed code can load & call into Win32 DLLs
    • Type information for Win32 DLLs is sorely lacking
      • export table contains only names & relative addresses of symbols
      • does not indicate whether exported symbol is a function or global variable
      • does not indicate “shape” of functions (its signature) or variables (its type)
    • The partnership
      • programmer describes function location & signature in terms of CLR types
      • managed compiler produces assembly/type metadata that drives JIT compilation
      • JIT compiler uses metadata to build “stubs” that perform CLR/Win32 transition
        • load the requested DLL (LoadLibrary)
        • locate the target function in memory (GetProcAddress)
        • marshal parameters to/from the target function

P/Invoke Mechanics

  • The CLR/Win32 transition is carried out by stubs & helpers
    • stubs are little chunks of native code emitted by the JIT compiler
      • specific to the marshaling requirements dictated by the p/invoke declaration
    • helpers are native functions typically found in mscor*.dll
      • general purpose functions that don’t need to be tuned specifically to the target
      • examples:
        • convert a CLR System.String parameter into NUL-terminated ANSI string
        • given a collection of CAS (code access security) permissions, perform a demand/assert/etc

P/Invoke Metadata

  • Programmer-supplied metadata drives P/Invoke
    • Method prototype describes the native function in terms of CLR types
      • managed compiler emits metadata for the managed method (empty body)
      • managed method can be called like any other static method
    • JIT compiler uses metadata to load DLL and locate the exported method
    • JIT compiler uses metadata to build stubs that handle the transition
using System.Runtime.InteropServices;
internal class Program
{
    static void Main()
    {
        int sum = Add(2, 2);
    }
    [DllImport("nativecalc.dll")]
    private static extern int Add(int a, int b);
}

P/Invoke Fine Tuning

  • Stub generation can be fine-tuned as needed
    • Using properties of DllImportAttribute
      • EntryPoint
      • CharSet
      • SetLastError
      • Others
    • By applying additional attributes to parameters and/or types
      • MarshalAsAttribute
      • StructLayoutAttribute
        • Lets you control the physical layout of the data fields of a class or structure
        • E.g.: [StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]

Example: P/Invoke Fine Tuning

// Exported by weirdtextutils.dll
//
BSTR WINAPI Concat( wchar_t *s1, char *s2 );
internal class Program
{
    private static void Main()
    {
        string s = Concat("Hello, ", "world!");
        Console.WriteLine(s);
    }
    [DllImport("weirdtextutils.dll")]
    [return: MarshalAs(UnmanagedType.BStr)] // Basic string
    private static extern string Concat(
        [MarshalAs(UnmanagedType.LPWStr)] string s1, // Unicode string
        [MarshalAs(UnmanagedType.LPStr)] string s2); // ANSI string
}

COM Interop: CLR => COM

  • Managed code can interact fairly easily with COM code
    • COM type information (TLB) is fairly complete
  • The partnership
    • TLBIMP.EXE translates COM type info into CLR type info
      • .TLB => TLBIMP.EXE => .DLL (“interop assembly”)
      • COM coclass => CLR class (concrete)
      • COM interface => CLR interface
      • automated by VS.NET Add Reference wizard
    • Programmer adds a reference to interop assembly
    • CLR/JIT compiler use metadata to construct “runtime-callable wrappers”
      • RCWs

Runtime-Callable Wrappers (RCW)

  • RCWs bridge the divide between two very different worlds
Type managed call site native COM component
Instantiation: operator new CLSID/ ProgId registry mappings CoCreateInstance
Type navigation: is/as/cast IUnknown :: QueryInterface
Error handling: System.Exception throw/try/catch/finally IErrorInfo / ( SetGet ) ErrorInfo Win32 SEH (Structured Exception Handling)
Memory management: shared heap garabage collection CoTaskMem ( Alloc / Free) IUnknown :: AddRef /Release

COM Interop Fine Tuning

  • COM type information (TLB) does not quite offer full fidelity
    • programmer manually describes types in CLR terms
    • attributes are used to provide required instantiation and marshaling details
    • CLR/JIT compiler uses programmer-supplied metadata to construct RCW
[ComImport, Guid("37DE3F74-99BE-4DD9-A06D-422203752987")]
class CalcClass
{
}

[Guid("22E8E9BF-552E-4A09-8B93-33778020F240"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ICalc
{
    int Add(int a, int b);
    int Sum([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] values, int count);
}
static void Main()
{
    ICalc c = (ICalc)new CalcClass();
    var values = { 1, 2, 3, 4 }; // 10
    int sum = c.Sum(values, values.Length);
    Console.WriteLine(sum);
}

COM & Threads

  • COM has an “apartment model” construct for dealing with threads
    • Components declare their preparedness/requirements re calling threads
      • ThreadingModel registry setting
    • Calling threads declare their preparedness/requirements re components
      • Colnitialize(Ex)
    • COM activation returns proxies or not based on those two settings
      • CoCreatelnstance(Ex)
  • CLR threads have a property that declares their COM apartment state
    • System.Threading.Thread.ApartmentState
    • System.STAThreadAttribute
    • System.MTAThreadAttribute
[STAThread]
private static void Main()
{
    var c = (ICalc)new CalcClass();
    var sum = c.Add(2, 3));
}

Resources