I know the answer (it's 42)

A blog on coding, .NET, .NET Compact Framework and life in general....

November, 2012

Posts
  • I know the answer (it's 42)

    C++/CLI and mixed mode programming

    • 2 Comments
    beach

    I had very limited idea about how mixed mode programming on .NET works. In mixed mode the binary can have both native and managed code. They are generally programmed in a special variant of the C++ language called C++/CLI and the sources needs to be compiled with /CLR switch.

    For some recent work I am doing I had to ramp up on Managed C++ usage and how the .NET runtime supports the mixed mode assemblies generated by it. I wrote up some notes for myself and later thought that it might be helpful for others trying to understand the inner workings.

    History

    The initial foray of C++ into the managed world was via the managed extension for C++ or MC++. This is deprecated now and was originally released on VS 2003.  This MC++ syntax turned out to be too confusing and wasn’t adopted well. The MC++ was soon replaced with C++/CLI. C++/CLI added limited extension over C++ and was more well designed so that the language feels more in sync with the general C++ language specification.

    C++/CLI

    The code looks like below.

    ref class CFoo
    {
    public:
        CFoo()
        {
            pI = new int;
            *pI = 42;
            str = L"Hello";
        }
    
        void ShowFoo()
        {
            printf("%d\n", *pI);
            Console::WriteLine(str);
        }
    
        int *pI;
        String^ str;
    };

    In this code we are defining a reference type class CFoo. This class uses both managed (str) and native (pI) data types and seamlessly calls into managed and native code. There is no special code required to be written by the developer for the interop.

    The managed type uses special handles denoted by ^ as in String^ and native pointers continue to use * as in int*. A nice comparison between C++/CLI and C# syntax is available at the end of http://msdn.microsoft.com/en-US/library/ms379617(v=VS.80).aspx. Junfeng also has a good post at http://blogs.msdn.com/b/junfeng/archive/2006/05/20/599434.aspx

    The benefits of using mixed mode

    1. Easy to port over C++ code and take the benefit of integrating with other managed code
    2. Access to the extensive managed API surface area
    3. Seamless managed to native and native to managed calls
    4. Static-type checking is available (so no mismatched P/Invoke signatures)
    5. Performance of native code where required
    6. Predictable finalization of native code (e.g. stack based deterministic cleanup)

     

    Implicit Managed and Native Interop

    Seamless, static type-checked, implicit, interop between managed and native code is the biggest draw to C++/CLI.

    Calls from managed to native and vice versa are transparently handled and can be intermixed. E.g. managed --> unmanaged --> managed calls are transparently handled without the developer having to do anything special. This technology is called IJW (it just works). We will use the following code to understand the flow.

    #pragma managed
    void ManagedAgain(int n)
    {
        Console::WriteLine(L"Managed again {0}", n);
    }
    
    #pragma unmanaged
    void NativePrint(int n)
    {
        wprintf(L"Native Hello World %u\n\n", n);
        ManagedAgain(n);
    }
    
    #pragma managed
    
    void ManagedPrint(int n)
    {
        Console::WriteLine(L"Managed {0}", n);
        NativePrint(n);
    }
    

    The call flow goes from ManagedPrint --> NativePrint –> ManagedAgain

    Native to Managed

    For every managed method a managed and an unmanaged entry point is created by the C++ compiler. The unmanaged entry point is a thunk/call-forwarder, it sets up the right managed context and calls into the managed entry point. It is called the IJW thunk.

    When a native function calls into a managed function the compiler actually binds the call to the native forwarding entry point for the managed function. If we inspect the disassembly of the NativePrint we see the following code is generated to call into the ManagedAgain function

    00D41084  mov         ecx,dword ptr [n]         // Store NativePrint argument n to ECX
    00D41087  push        ecx                       // Push n onto stack
    00D41088  call        ManagedAgain (0D4105Dh)   // Call IJW Thunk
    
    

    Now at 0x0D4105D is the address for the native entry point. If forwards the call to the actual managed implementation

    ManagedAgain:
    00D4105D  jmp         dword ptr [__mep@?ManagedAgain@@$$FYAXH@Z (0D4D000h)]  
    

    Managed to Native

    In the case where a managed function calls into a native function standard P/Invoke is used. The compiler just defines a P/Invoke signature for the native function in MSIL

    .method assembly static pinvokeimpl(/* No map */) 
            void modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) 
            NativePrint(int32 A_0) native unmanaged preservesig
    {
      .custom instance void [mscorlib]System.Security.SuppressUnmanagedCodeSecurityAttribute::.ctor() = ( 01 00 00 00 ) 
      // Embedded native code
      // Disassembly of native methods is not supported.
      //  Managed TargetRVA = 0x00001070
    } // end of method 'Global Functions'::NativePrint
    
    

    The managed to native call in IL looks as

    Manged IL:
      IL_0010:  ldarg.0
      IL_0011:  call void modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) NativePrint(int32)
    

    The virtual machine (CLR) at runtime generates the correct thunk to get the managed code to P/Invoke into native code. It also takes care of other things like marshaling the managed argument to native and vice-versa.

    Managed to Managed

    While it would seem this should be easy, it was a bit more convoluted. Essentially the compiler always bound to native entry point for a given managed method. So a managed to managed call degenerated to managed -> native -> managed and hence resulted in suboptimal double P/Invoke. See http://msdn.microsoft.com/en-us/library/ms235292(v=VS.80).aspx

    This was fixed in later versions by using dynamic checks and ensuring managed calls always call into managed targets directly. However, in some cases managed to managed calls still degenerate to double P/Invoke. So an additional knob provided was the __clrcall calling convention keyword. This will stop the native entry point from being generated completely. The pitfall is that these methods are not callable from native code. So if I stick in a __clrcall infront of ManagedAgain I get the following build error while compiling NativePrint.

    Error	2	error C3642: 'void ManagedAgain(int)' : cannot call a function with
    __clrcall calling convention from native code <filename>

    /CLR:PURE

    If a C++ file is compiled with this flag, instead of mixed mode assembly (one that has both native and MSIL) a pure MSIL assembly is generated. So all methods are __clrcall and the Cpp code is compiled into MSIL code and NOT to native code.

    This comes with some benefits as in the assembly becomes a standard MSIL based assembly which is no different from another managed only assembly. Also it comes with some limitation. Native code cannot call into the managed codes in this assembly because there is no native entry point to call into. However, native data is supported and also the managed code can transparently call into other native code. Let's see a sample

    I moved all the unmanaged code to a separate /C++:CLI dll as

    void NativePrint(int n)
    {
        wprintf(L"Native Hello World %u\n\n", n);
    }
    

    Then I moved my managed C++ code to a new project and compiled it with /C++:PURE

    #include "stdafx.h"
    #include 
    
    #include "..\Unmanaged\Unmanaged.h"
    using namespace System;
    
    void ManagedPrint(int n)
    {
        char str[30] = "some cool number";     // native data  
        str[5] = 'f';                          // modifying native data
        Console::WriteLine(L"Managed {0}", n); // call to BCL
        NativePrint(n);                        // call to my own native methods
        printf("%s %d\n\n", str, n);           // CRT
    }
    
    int main(array ^args)
    {
        ManagedPrint(42);
        return 0;
    }
    

    The above builds and works fine. So even with C/++:PURE I was able to

    1. Use native data like a char array and modify it
    2. Call into BCL (Console::WriteLine)
    3. Call transparently into other native code without having to hand generate P/Invoke signatures
    4. Use native CRT (printf)

    However, no native code can call into ManagedPrint. Also do note that even though Pure MSIL is generated, the code is unverifiable (think C# unsafe). So it doesn't get the added safety that the managed runtime provides (e.g. I can just do str[200]  = 0 and not get any bounds check error)

    /CLR:Safe

    /CLR:safe compiler switch generates MSIL only assemblies whose IL is fully verifiable. The output is not different from anything generated from say C# or VB.NET compilers. This provides more security to the code but at the same time losses on several capabilities over and above the PURE variant

    1. No support for CRT
    1. Only explicit P/Invokes

    So for /CLR:Safe we need to do the following

    [DllImport("Unmanaged.dll")]
    void NativePrint(int i);
    
    void ManagedPrint(int n)
    {
        //char str[3000] = "some cool number"; // will fail to compile with  
        //str[5] = 'f';                        // "this type is not verifiable"
    
        Console::WriteLine(L"Managed {0}", n);
    
        NativePrint(n);                        // Hand coded P/Invoke
    

    Migration

    MSDN has some nice articles on people trying to migrate from /CLR to

    1. To /CLR:Pure http://msdn.microsoft.com/en-US/library/ms173253(v=vs.80).aspx
    1. To /CLR:Safe http://msdn.microsoft.com/en-US/library/ykbbt679(v=vs.80).aspx
  • I know the answer (it's 42)

    Windows Phone 8: Evolution of the Runtime and Application Compatibility

    • 3 Comments

    Long time back at the wake of the release of Windows Phone 7 (WP7) I posted about the Windows Phone 7 series programming model. I also published how .NET Compact framework powered the applications on WP7.

    Further simplifying the block diagram, we can think of the entire WP7 application system as followsimage

    As with most block diagrams, this is gross simplifications. However, I hope it helps to easily picture the entire system.

    Essentially the application can be purely managed (written in say C# or VB.net). The application can only utilize services exposed by the developer platform and core services provided by .NET Compact Framework. The application can in no way directly use native code or talk to the OS (say call an Win32 API). It has to always go through the runtime infrastructure and is in a security sandbox.

    The application manager is the loose term I am using to encompass everything that is used to managed the application including the host.

    Windows Phone 8 (WP8) is a huge huge change from Windows Phone 7.x (WP7). From the perspective of a WP7 application running on a WP8 device the system looks as follows

    image

    Everything in Green in this diagram got outright replaced with entire new codebase and the rest of the system other than the application was heavily modified to work with the new OS and the new managed runtime.

    Shared Windows Core

    The OS moved away from Windows Compact Embedded (WinCE) OS core that was used in WP7 to a new OS which shares it’s core with the desktop Windows 8. This means that a bunch of things in the WP8 OS is shared with the desktop implementation, this includes things like kernel, networking, driver framework and others. The shared core obviously brings great value as innovations and features will more easily flow across the two form factors (device and desktop) and also reduce engineering redundancy on Microsoft side. Some of the benefits are readily visible today like great multi-core support, WinRT interop and others are more subtle.

    CoreCLR

    .NET Compact Framework (NETCF) that was used in WP7 has a very different design philosophy and hence a completely different implementation from the desktop .NET. I will have a follow up post on this but for now it suffices to note that .NETCF is a very portable runtime that is designed to be very versatile and cross platform. Desktop CLR on the other hand is more closely tied with Windows and the processor architecture. It closely works with the OS and the underlying HW to give the maximum performance benefit to managed code running on it.

    With the new Windows RT which works on ARM, desktop CLR was anyway updated to work on the ARM processor. So when the phone chose to move to shared core it was an obvious choice to move the CLR as well. This gave the same benefits of shared innovation and reduced engineering redundancy.

    The full desktop CLR is more heavy and provides functionality that is not really required by the phone scenarios and hence a lighter variant of it (which is built from the same source) called CoreCLR was chosen for WP8. CoreCLR is the evolution of the lightweight runtime that powered Silverlight. With the move to CoreCLR developers get a much faster runtime with extended feature set that includes interop via WinRT.

    Backward Compat

    One of the simple statements made during all of these WP8 launch presentations was that applications in the store built for WP7 will work as is for WP8. This is a small statement but is a huge achievement and effort from the runtime implementation perspective. Making apps work as is when the entire runtime, OS and chipset has changed is non-trivial to say the least. Our team worked very hard to make this possible.

    One of the biggest things that played out to our benefit was that the WP7 apps were fully sandboxed and couldn’t use any native code. This means that they didn’t have any means of taking behavioral dependence on the OS APIs or underlying HW. The OS APIs were used via the CLR and it could always add quirks to expose consistent behavior to the applications.

    API Compatibility
    This required us to ensure that CoreCLR exposes the same API set as NETCF. We ran various automated tools to manage the API surface area changes and retain meaningful API compat between WP7 and WP8. With a closed application store it was possible for us to gather complete metrics on API usage and correctly prioritize engineering resources to ensure that majority of applications continued to see the same API set in signature and semantics.

    We also needed to ensure that the same APIs behave as closely as possible with that provided by NETCF. We tested a lot of applications in the app store to get as close as we can and believe that we are at a place that should allow most WP7 application’s API usage to transparently fall over to the new runtime.

    Runtime behavior changes
    When a runtime changes there are behavioral changes that can expose pre-existing issues with applications. This includes things like timing differences. Even though these runtime behaviors are not documented, or in some cases especially called out that user code should not take dependence on them, some apps still did do that.

    Couple of the examples we saw

    1. Taking dependence on finalization order
      Even though CLI specification clearly calls out that finalization order in .NET is not deterministic, code still took subtle dependency on them. In one particular case object F1 used a file and it’s finalizer released it. Later another object F2 opens the same file. With change in the runtime, the timing of GC changed so that at the time F2 tried to open the file, F1 which has been already collected hasn’t yet had its finalizer run. Hence the application crashed. We got in touch with the app developer and got the code moved to use the right dispose pattern
    2. GC Timing and changes in number of generations
      Application finalizers contrary to .NET guidelines modified other managed objects. Now changed GC timings and generation resulted in those objects to have already been collected and hence resulted in finalizer crashes
    3. Threading issues
      This was one of the painful ones. With the change in OS and hence thread scheduler and addition of multiple cores in the ARM CPU, a lot of subtle races and deadlocks in the applications got exposed. These were pre-existing issues where synchronization primitives were not used correctly or some code relied on the quanta based thread scheduling WinCE did. With move to WP8, threads run in parallel on different cores and scheduled in different order, this lead to various deadlocks and race driven crashes. Again we had to get in touch with app developer to address these issues
    4. There were other cases where exact precision of floating point math was relied on. This resulted in a board game where pucks flew off from its surface
    5. Whether or not functions got inlined
    6. Order of static constructor initialization (especially in conjunction with function in-lining)

    We addressed some of these issues where it was realistic to fix them in the runtime. For some of the others we got in touch with the application developers. Obviously all applications in the store and all of their features were not tested. So you should try to test your applications when you have access to WP8.

    At one point we were all playing games on our phones telling our managers that I am compat testing Need For Speed and I need to test till level 10 :)

Page 1 of 1 (2 items)