Interop 101 – Part 1

It's funny how often the people within our team (myself included) take certain things for granted. We have provided a great way to bridge the gap between native and managed code with C++/CLI yet I am continually surprised by how little information has been successfully conveyed. I posted slides from the talk I gave last month on this topic, however it lacks the most basic examples. To this effect, I intend to write up a few posts with some simple samples to boil down the basic concepts behind what we have dubbed C++ Interop.


Let's start with "legacy" native Win32 code. The following piece of code defines a simple type that is exported from a dll.


   // HelloWorld.h

   #pragma once


   class __declspec(dllexport) HelloWorld






      void SayThis(wchar_t *phrase);



The implementation of the SayThis method just brings up a message box as follows:


   void HelloWorld::SayThis(wchar_t *phrase)


      MessageBox(NULL, phrase, L"Hello World Says", MB_OK);



Once this code is compiled into a dll, our goal is to instantiate HelloWorld and invoke its one and only method. A traditional native client would do it by including the header we defined above as follows:


   #include "..\interop101\helloworld.h"


   int wmain()


      HelloWorld hw;

      hw.SayThis(L"I'm a native client");

      return 0;



Alright, now that I've reminded what native code looks like, we can talk interop. In this little educational series, I intend to demonstrate 4 major scenarios.

  1. Client in C++/CLI calls the native code

  2. Client in C# calls the native code using its built-in interop functionality (a.k.a. P/Invoke)

  3. Client in C# calls the native code via a COM interface

  4. Client in C# calls the native code via a C++/CLI wrapper

Today let's look at the first and simplest scenario: using C++/CLI.


   #include "..\interop101\helloworld.h"


   using namespace System;


   int main(array<System::String ^> ^args)


      HelloWorld hw;

      hw.SayThis(L"I'm a managed C++ client");

      return 0;



Hmmm… Looks virtually identical… This what we refer to when we talk about IJW or "It Just Works". In other words, all we had to do here was throw the /clr switch on the original code and the compiler generated MSIL instead of x86 assembly. If we delve into the MSIL, we see that transitions to native code (there are three in this case, can you spot them?) are automagically handled by the compiler.


.method assembly static int32  main(string[] args) cil managed


  // Code size       51 (0x33)

  .maxstack  2

  .locals ([0] int32 V_0,

           [1] int32 V_1,

           [2] valuetype HelloWorld hw)

  IL_0000:  ldc.i4.0

  IL_0001:  stloc.0

  IL_0002:  ldloca.s   hw

  IL_0004:  call       valuetype HelloWorld* modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall) 'HelloWorld.{ctor}'(valuetype HelloWorld* modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst))

  IL_0009:  pop



    IL_000a:  ldloca.s   hw

    IL_000c:  ldsflda    valuetype '<CppImplementationDetails>'.$ArrayType$$$BY0BJ@$$CB_W modopt([mscorlib]System.Runtime.CompilerServices.IsConst) '?A0x783d98d5.unnamed-global-0'

    IL_0011:  call       void modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall) HelloWorld.SayThis(valuetype HelloWorld* modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst),


    IL_0016:  ldc.i4.0

    IL_0017:  stloc.1

    IL_0018:  leave.s    IL_0028

  }  // end .try



    IL_001a:  ldftn      void modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall) 'HelloWorld.{dtor}'(valuetype HelloWorld* modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst))

    IL_0020:  ldloca.s   hw

    IL_0022:  call       void ___CxxCallUnwindDtor(method void *(void*),


    IL_0027:  endfinally

  }  // end handler

  IL_0028:  ldloca.s   hw

  IL_002a:  call       void modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall) 'HelloWorld.{dtor}'(valuetype HelloWorld* modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst))

  IL_002f:  ldloc.1

  IL_0030:  stloc.0

  IL_0031:  ldloc.0

  IL_0032:  ret

} // end of method 'Global Functions'::main


Voila. Short and sweet this time. Next example will be far more useful for C# clients 🙂


Comments (13)
  1. boogie says:

    Say I have

    typedef struct myS myS;

    struct myS {

     int  i32;

     void *ptr;


    myS s;

    s.i32 = 1;

    s.ptr = some_ptr_to_who_knows_what;

    int myRet = CallMe(&s);

    Such a simple non-hello-world would be more useful, because that’s what will be used in the real world.  You know, pointers.

    I can’t say the IL is at all useful (on spending column-inches), not without description, and still not useful even then, I would think.  Maybe when you get to #4 that’ll be a good time?

  2. borisj says:

    Points taken boogie. I’ll try to do better in the next posts.

  3. Eric says:

    Yes, How do we declare types and structs in CLR? I do not think it is possible. I am trying to integrate some old .libs (C) into a CLR, is this possible?

Comments are closed.

Skip to main content