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
{
public:
HelloWorld();
~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.
Today let's look at the first and simplest scenario: using C++/CLI.
using namespace System;
int main(array<System::String ^> ^args)
hw.SayThis(L"I'm a managed C++ client");
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
.try
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),
char*)
IL_0016: ldc.i4.0
IL_0017: stloc.1
IL_0018: leave.s IL_0028
} // end .try
fault
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*),
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 :)