Welcome to MSDN Blogs Sign in | Join | Help

I'm not going to write a long note explaining once again that I've been too busy to post. We all know it's not true and who wants to sound like a hypocrite right?

In any case, my colleague Luke recently posted an entry about a feature we both worked on and I wanted to share the cool news as a foray into talking about Visual Studio 2008 (and perhaps more!). We dubbed the feature ".NET Framework Multitargeting" and it does indeed sound like a mouthful. I'm not going to describe it in gory detail since Luke already did a great job of this, but I will clarify a couple of points.

The first and most important thing is to understand what the feature is (and what it isn't). The goal of multitargeting is to make it easier to build applications and components such that they will run on one of the 3 frameworks based on CLR 2.0:

  • .NET Framework 2.0 - the one we released with VS2005
  • .NET Framework 3.0 - the one we released with Vista
  • .NET Framework 3.5 - the one we are releasing with VS2008

Notice that I said all 3 of these run on CLR 2.0, which means that all of these work with the same runtime. We are not performing runtime multitargeting (although this was oft-requested in the 1.1->2.0 migration) as it involves significantly more complexity but we do want to tackle this in the future as we upgrade the runtime.

So what does multitargeting enable if not the ability to switch runtimes? It helps you understand which assemblies are available in which framework in order to facilitate building and deploying applications for a specific minimum framework version. Over time, with the advent of Silverlight and the next version of the CLR, I hope we will extend this notion into a complete and general solution that enables developers to use the latest development tool with the deployment target of their choice.

As for you, the C++ developer, there are a couple of points in the UI that Luke does not mention since the project system for VC++ is not the same as C# and VB. In order to choose your preferred framework target for a C++ project, you go to the project properties pages, under the "Common Properties" section as shown in this screenshot.

c   multitargeting 1

As you might remember, this property page is not per-configuration so you can't use this facility to create a set of configurations for each deployment target for your application. I would have liked to do this, but first we wanted to keep it simple, second, the idea of building an app for multiple different minimum deployment framework targets seemed rare.

Anyway, I hope you enjoy this feature, and please give us your feedback as you can play with this in VS2008 Beta 2.

As overdue as this post is, let's just jump in. In my first 4 installments, I focused on the different ways you could access native functionality from managed code. In this post, I will flip the actors around and investigate how to expose managed functionality to native clients.

The first thing to note is that COM Interop can always solve this problem. Using very little work, you can take a managed assembly and use built-in framework tools to generate a COM shim for native C++ (or VB) clients. Of course that means your calling code has to go through COM and if this is solely for the purpose of interop and you don't need to use COM as your component technology then the performance cost is absolutely not worth it. Thus, once again, C++/CLI will save us :-)

In our little story, I have a HelloWorld C# type that looks like this:

 

public class HelloWorld

{

private static int Counter = 0; 

public void Speak()

{

MessageBox.Show("Hello World #" + Counter++);

}

}

 

Now we have a C++ client that wants to access this functionality (apparently, this user doesn't know that MessageBox exists as a purely native API). The simplest way to access it is to compile the file where the calling code is using /clr and then instantiating this object and calling the method. Super easy. Hold on, the client just called and said the code must remain 100% native. Why? Who knows :-) In this case, we'll have to create an inverted wrapper that provides a purely native interface. Here we go…

The execution is simple: build a new DLL that is compiled /clr but that exposes a native class as opposed to a reference class (remember, C++/CLI preserves native semantics and automagically exports things correctly).

// NativeHello.h

 

#ifdef _MANAGED

#using <HelloWorld.dll>

#include <vcclr.h>

#else

#include <stddef.h>

#endif

 

class NativeHello

{

private:

#ifdef _MANAGED

gcroot<HelloWorld^> hw;

#else

intptr_t hw;

#endif

public:

__declspec(dllexport) NativeHello();

__declspec(dllexport) ~NativeHello();

__declspec(dllexport) void Speak();

};

 

With the following implementation.

NativeHello::NativeHello()

{

// initialize managed hello world

hw = gcnew HelloWorld();

}

 

NativeHello::~NativeHello()

{

// nothing to do :)

}

 

void NativeHello::Speak()

{

hw->Speak();

}

 

Let's go through each part of this in turn. At the top, I enclosed two statements within a check to see if we are compiling as managed (i.e. with /clr). The #using statement is essentially the equivalent of #import for COM. For C# programmers out there, this statement is equivalent to adding a reference to an assembly. The #include statement introduces the gcroot abstraction, which I'll talk about in a second. Now why did I enclose these statements in #ifdef _MANAGED? Our goal is to create a DLL that can be accessed by a purely native client and unfortunately in the native world, libraries do not (exactly) describes themselves and we need to use a header file as the descriptor. When a native client includes our native wrapper header file, the code enclosed within the _MANAGED block will be ignored. This is necessary since these statements only make sense for managed compilands. Luckily, the native client only needs to know about the types/functions we're exporting and hiding these statements has no ill effect. The #else clause adds an #include for intptr_t mentioned below.

Our wrapper type is then declared with a private member called hw, which is of type gcroot< HelloWorld^>. In the converse example from my previous posts, we simply embedded a native pointer as a private member. The fact is, you can't have a handle embedded in a native type so the gcroot template creates an (seamless) indirection by using the BCL's GCHandle value type, which enables the native code to hold a managed object and prevents the CLR's garbage collection of the object. However, this template only makes sense when we're compiling managed. Thus, in the case of a native includer, the gcroot member is stored as a simple intptr_t, which has the same size as gcroot on any platform.

Of course, we need to start exporting some real functionality! The constructor, destructor and the Speak method are all exported in the traditional native manner using __declspec(dllexport). None of these prototypes expose the managed implementation as is our goal. In other words, here is the view of the wrapper class from a native client.

 

class NativeHello

{

private:

intptr_t hw;

public:

NativeHello();

~NativeHello();

void Speak();

};

 

Voila. We now have a wrapper for purely native clients that are unable to use /clr (VC6 clients I presume!). Of course, you should make sure these clients use the same compiler, unless you want to open the way for the most obscure bugs on the planet, nay, in the galaxy(mixing CRTs leads to the dark side).

If you find yourself needing this type of wrapper often, you should go take a peek at Paul DiLascia's generic version of this sample that generates wrappers for any managed type.

It seems as though my blogging comes in phases, with short periods of writing often interspersed with (very) long periods of silence :) Heck, in this specific instance, I even let my interop series go unfinished. In my next post on the topic, I plan to talk about exposing managed APIs to purely native clients (granted, not the most common scenario, but someone asked and it's not too hard).

As for this post, I've decided to use it to brag about this past weekend, which has been the greatest mix of work-related fun I've had since I joined Microsoft… I must warn you, you might get jealous (or simply unbelievably bored).

As some of you may have heard, we shipped a new operating system today (V-something) and even a new office application suite (O-something). Leading up to this big bang evolves decades of "man-hours" (completely random guess on my part) and one of the things we like to do at Microsoft is Beta Test. To this effect, months ago, we started an internal competition to encourage employees across the company to dogfood Vista, with the promise of prizes ahead. For every day that we ran the OS on our machine at work, we would get a point and in addition, there was a bunch of ways to get some more points here and there. Once all was said and done and we had played our part in shipping the company's flagship product, there was a drawing amongst the users in the top tier (points-wise). For some reason (let's call it luck), I won the grand prize: an all-expense paid trip to New York to see the #1 Vista launch event in the World. It was a great show with Bill and Steve in attendance and I would post pictures but I have yet to figure out how to work the photo gallery on our blog servers. Needless to say, hanging out in New York for a long weekend is plain awesome, especially for someone from the East coast like myself. Having even lived in Paris for many years, I would still say that the Big Apple is the greatest (f-in) city in the world (although the former remains the most beautiful one). It's been great and I leave tomorrow for a trip to Cairo where I'll be speaking at the Middle East Developer Conference. More details on that to follow.

Here's to another bout of blogging from yours truly.

I apologize for the long delay for this section (although I suppose my average posting frequency is already pretty low), but I was on a much needed vacation. I finished the last chapter with a brief mention of what I would talk about now, which is the native support for interop that C++ provides. In a sense, I hope this is going to appear to be the simplest method even though I will introduce a few new concepts and use C++/CLI, which adds new language constructs to C++ in order to express .NET semantics (e.g. garbage collected types).

As always, let us reprise our original HelloWorld example. I'm going to include it again for sake of making this post depend as little as possible on the previous ones.

// HelloWorld.h

#pragma once

 

class __declspec(dllexport) HelloWorld

{

public:

      HelloWorld();

      ~HelloWorld();

 

      void SayThis(wchar_t *phrase);

};

// HelloWorld.cpp

void HelloWorld::SayThis(wchar_t *phrase)

{

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

}

Our goal is to access this type from .NET. As it stands, this piece of code already compiles into a native DLL. The question that stands before us first is what clients will access this code from now on. In other words, are we replacing all existing client code of this DLL with managed code or are we going to maintain some purely native clients. In the first case, we can write our wrapper code directly into the DLL and compile it into a managed assembly (with native code backing it). In the second case, we need to create a second DLL that will be a native client to this one while publishing a managed interface for .NET clients. It is the latter case that we are going to jump into now.

The first thing to do is to create a new CLR project, which we can do with a wizard (look under the Visual C++ > CLR node in the New Project dialog) or simply taking a blank slate and making the project compile with the /clr switch. This switch is the cornerstone of this entire scenario. If you remember the first part in this series, we showed how the C++ compiler is able to generate MSIL and furthermore, it can generate a process image with both a managed and a native section (the only compiler capable of doing so I might add). We have yet to really lay down the bricks for our wrapper so let's make a naïve wrapper for HelloWorld now.

// cppcliwrapper.h

#pragma once

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

 

namespace cppcliwrapper {

 

      class ManagedHelloWorld

      {

      private:

            HelloWorld hw;

      public:

            ManagedHelloWorld();

            ~ManagedHelloWorld();

 

            void SayThis(wchar_t *phrase);

      };

}

This piece of code is a native wrapper around our native type using traditional OO encapsulation. Even though this piece of code will compile into MSIL, it does not solve our original problem. Why is that? It's because we're still dealing with a native type. In other words, the ManagedHelloWorld class still obeys the rules of native semantics, namely the fact that it must live on the native heap. Managed languages like C# have no knowledge of the native heap and their new operator only instantiates objects into the CLR's heap. We need to make this wrapper a managed type, which will have the same semantics as a class in C#. Enter C++/CLI. With these additions to the language, we can create two new types of classes: managed value and reference types (the difference is mainly in the way they are implicitly copied). For our wrapper, we simply need to change its declaration from class to ref class. Once we compile the resulting code, we get a pivotal error.

error C4368: cannot define 'hw' as a member of managed 'ManagedHelloWorld': mixed types are not supported

What could this possibly mean? This error is actually directly related to the problem we described just above. In order to be a proper managed reference type that C# and other managed languages can instantiate, we cannot encapsulate native members. Indeed, our wrapper cannot live on the CLR's managed heap as it contains a member that can only live on the native heap. We can resolve this issue by encapsulating a pointer to our native type. Thus we have the following wrapper code.

ref class ManagedHelloWorld

{

private:

      HelloWorld *hw;

public:

      ManagedHelloWorld();

      ~ManagedHelloWorld();

 

      void SayThis(wchar_t *phrase);

};

Only three things remain in order to make this wrapper usable. The first is to make it public in accordance with .NET accessibility rules. The second is to change the interface of SayThis such that it uses a managed string. The third is to include the implementation! So here it goes.

// cppcliwrapper.cpp

#include "cppcliwrapper.h"

#include "marshal.h"

 

using namespace cppcliwrapper;

 

ManagedHelloWorld::ManagedHelloWorld() : hw(new HelloWorld())

{

}

 

ManagedHelloWorld::~ManagedHelloWorld()

{

      delete hw;

}

 

void ManagedHelloWorld::SayThis(System::String^ phrase)

{

      hw->SayThis(marshal::to<wchar_t*>(phrase));

}

There are two notable elements we have introduced in this final piece code, the managed handle and data marshalling. The handle or "hat" (or "accent circonflexe" even) is part of the C++/CLI language. It represents a pointer to a managed object. Other languages like Java, C# and VB don't use anything like this as they no longer have native semantics. However C++ needs to differentiate between the stack, the native heap and the managed heap and it does so using * and ^. Data marshalling is a huge topic and can eventually become one of the more complex things you have to manage when working with interop. In this example, we need to convert a managed String into a native pointer to wchar_t. In order to do this, a great pattern is to create a library of static template functions, which thus remain stateless and help maintain a certain level of consistency. In this example, we created the following functions:

namespace marshal {

      template <typename T>

      static T to(System::String^ str)

      {

      }

 

      template<>

      static wchar_t* to(System::String^ str)

      {

            pin_ptr<const wchar_t> cpwc = PtrToStringChars(str);

            int len = str->Length + 1;

            wchar_t* pwc = new wchar_t[len];

            wcscpy_s(pwc, len, cpwc);

            return pwc;

      }

}

 

After this is all said and done, we compile our code into an assembly that 3rd party .NET clients can use as if it were written in C#. So here is our resulting client code, which is eerily similar to the COM example.

 

using System;

using System.Text;

using cppcliwrapper;

 

namespace CSharpDirectCaller

{

    class Program

    {

        static void Main(string[] args)

        {

            ManagedHelloWorld mhw = new ManagedHelloWorld();

            mhw.SayThis("I'm a C# application calling native code via C++ interop!");

        }

    }

}

 

I have a lot more to say about this, and I promised a performance comparison as well as a 5th part describing doing this in reverse. My next post should not be so long in the making.

 

In the last part of this little series, we looked into how C# (and .NET languages in general) can call into native code as directly as possible through P/Invoke. While this is a viable technique in many cases, it doesn’t scale to complex interop nicely. Users of Java probably recognized it as something quite similar to JNI (Java Native Interface). Of course, as I mentioned in my first post on the subject, there are a number of ways to perform interop on Windows and in this part we are going to delve into COM interop.

 

The Component Object Model or COM does not need much introduction by now, as it’s been around since for over 10 years now. COM was actually designed to solve a more general interop problem than the native/managed issue we’re looking at. I have yet to come up with my own great one-liner to describe COM so I’ll quote Wikipedia to say that “The essence of COM is a language-neutral way of implementing objects such that they can be used in environments different from the one they were created in, even across machine boundaries.” Sounds like a perfect candidate for interop with managed code, after all, what’s the CLR if not a difference type of machine (the JVM makes it even clearer by keeping the work machine in its name).

 

There are two “starting points” in my COM scenario. The first (and easiest) is when you already have a COM component. I’ve seen many companies that already wrap their functionality in COM (heck, just look at the number of COM type libraries that are installed on your box right now) so it’s a fairly common starting point. On the other hand, we have our old Hello World class, which is a simple ANSI C++ piece of code (granted it calls into a Win32 API but let’s imagine it’s something cross-platform). In these cases, you need to wrap this object into COM before we can discuss the interop story.

 

In my very humble opinion, COM is powerful but complicated, and wrapping functionality into COM can be a pain. Enter ATL and Visual C++ wizards, which are the easiest way of writing a COM component. First thing is the New Project dialog, where you should choose the ATL Project wizard and then choose to make it an attributed DLL on the application settings page (I’ll spare the screenshots of the wizard). Two projects are created, one of which is something of an implicit project. Now we have a COM object, which will be our Hello World wrapper object, so we need to do two things: encapsulate the original class and add SayThis to the COM object’s interface. The first part is traditional C++, just #include the HelloWorld.h header and adding a class which will encapsulate the original one. The second comes by using the Add Method wizard on the wrapper class. Ok what does this look like? Behold the magic of ATL code (believe me these few lines hide a lot of underlying functionality).

 

The COM interface

// IHelloWorld

[

      object,

      uuid("784C374B-2DAF-4ECF-BDE6-90360E7630B6"),

      dual, helpstring("IHelloWorld Interface"),

      pointer_default(unique)

]

__interface IHelloWorld : IDispatch

{

      [id(1), helpstring("method SayThis")] HRESULT SayThis([in] BSTR phrase);

};

 

 

The COM object declaration

// CHelloWorld

[

      coclass,

      default(IHelloWorld),

      threading(apartment),

      vi_progid("comwrapper.HelloWorld"),

      progid("comwrapper.HelloWorld.1"),

      version(1.0),

      uuid("9CCC02BA-2A46-423E-8E3B-80FB1DD6A6B1"),

      helpstring("HelloWorld Class")

]

class ATL_NO_VTABLE CHelloWorld : public IHelloWorld

{

public:

      CHelloWorld() : hw(new HelloWorld())

      {

      }

 

      ~CHelloWorld()

      {

            delete hw;

      }

 

      DECLARE_PROTECT_FINAL_CONSTRUCT()

 

      HRESULT FinalConstruct()

      {

            return S_OK;

      }

 

      void FinalRelease()

      {

      }

 

private:

      HelloWorld *hw; // The original encapsulated native HelloWorld

 

public:

      STDMETHOD(SayThis)(BSTR phrase); // The exported method

};

 

The COM object implementation

// CHelloWorld

STDMETHODIMP CHelloWorld::SayThis(BSTR phrase)

{

      hw->SayThis(phrase);

      return S_OK;

}

 

Alright, so this all compiles nicely into a DLL, you can register it with regsvr32.exe, and now we want to do invoke this functionality from C#!

After seeing the magic of ATL, now we’ll see the magic of the CLR and its built-in COM interop functionality. First thing to do is add a reference to our COM component. Nothing could be easier: just right-click on the references folder in our C# project and select “Add Reference”. In the resulting dialog, we select the COM tab and in the list we should find the component we created above. Once we add it, Visual Studio will do all sorts of wondrous things to fool the project that it has something that looks like a managed assembly. If our wrapper is called comwrapper then here is our resultant C# caller code.

 

using System;

using System.Collections.Generic;

using System.Text;

 

using comwrapper; // The COM DLL we created

 

namespace CSharpComCaller

{

    class Program

    {

        static void Main(string[] args)

        {

            CHelloWorld chw = new CHelloWorld(); // CHelloWorld is the COM wrapper object

            chw.SayThis("I'm a C# application calling native code via COM interop!");

        }

    }

}

 

We’re done! What do you think? Pretty elegant solution, no? It’s pretty close to perfect when you already have COM components. Is there a flip-side though? There are two. One is the fact that you need to create COM wrappers, which can be a hassle. The second is that all the magic of COM and COM interop comes at a price. Performance. Obviously, there’s always a performance price when doing any kind of interop, but if you have a simple native type and you want to call it from managed code, using COM interop has by far the worst performance cost. This fact brings us to the lesser-known yet infinitely more powerful and elegant method that is native C++ interop, which we will lay bare in our next installment of this series. Eventually, we’ll do a performance comparison as well so that I may back up this claim (probably not an extremely scientific one though).

 

In the meantime, if you’d like to learn more (there’s a lot) about COM interop, look here.

 

My colleague Kang Su (that's his first name) came by my office yesterday to borrow a book. You might think a story that starts like this couldn't possibly lead to anything interesting. This tale is compelling though so hang tight. So the book in question was Debugging Applications for Microsoft .NET and Microsoft Windows (what a mouthful!). It's supposedly a classic and Kang Su could not find his copy. As we were chatting, he decided to bring up the issue for which he wanted to borrow the book in the first place. Kang Su, being the most technical PM to grace the halls of Visual C++, wanted to debug Phoenix (the next generation compiler) using Visual Studio. The problem lies in the fact that he does not have a solution and the executable relies on a bunch of environment variables. He asked me for a way to launch the app, in the debugger, with the environment set, all in one fell swoop. Instead of letting him leaf through the 800+ pages of the tome he was taking, I decided to take a look at the command-line switches for devenv.exe. Lo and behold, the answer was simple.

> set ENV_VARIABLE=value

> devenv /useenv /debugexe myapp.exe [ <myapp arguments> ]

 

That's all folks!

In my last post, I began my little foray into basic managed/native interop scenarios. The goal is to discuss (in a first step at least) the different ways one can access native code from the managed world. Arguably, the simplest method is the one I used in part 1, which is to use C++/CLI and its built-in understanding of native code. There are other ways to do this, especially since C# (and VB) users also need to cross the barrier now and again.

In this entry, we're going to look into using P/Invoke via DLLImport. That may sound a little esoteric but it will be clear in no time. P/Invoke is short form for Platform Invoke, which is what the managed world calls a transition into native irrespective of the language one is coding in. In fact, in part 1, the compiler took care of generating the underlying P/Invoke whenever a transition needed to happen. DLLImport is a .NET attribute that allows you to define entry points into native libraries. It is the C# equivalent of feeding the C++ linker an input library. And now, on with the show.

The first thing we'll do is take a look at a typical usage of DllImport and apply that to our ongoing example.

[DllImport("User32.Dll")]

static extern Int32 MessageBox(UInt32 hWnd, String message, String Caption, UInt32 uType);

Seems pretty simple. This statement defines a static method in the current scope, which maps to a function exported in user32.dll. How we would go about applying this to the HelloWorld class from part 1? Previously we had instantiated the type and called its member function. Unfortunately, the technique we discuss in this part has a fundamental limitation. We cannot import types through DllImport, only functions. Thus, while DllImport is well-suited to C-style APIs like Win32, it becomes much more difficult with object-oriented C++ code. However, this is software and there's always a way. Let's dive in and find out what we need to do to make this work (and hopefully realize this is not a good way to go).

First, we need an instance. Native objects live in the native heap and cannot be instantiated by the CLR, which only works with the managed heap. So we need to expose the native object's "new" and "delete" functionality as flat, exported APIs. We can do this by adding two functions to our original piece of C++ such that it looks like this.

// HelloWorld.h

#pragma once

 

class __declspec(dllexport) HelloWorld

{

      int foo;

public:

      HelloWorld();

      ~HelloWorld();

 

      void SayThis(wchar_t *phrase);

};

 

extern "C" __declspec(dllexport) HelloWorld* HelloWorld_New();

extern "C" __declspec(dllexport) void HelloWorld_Delete(HelloWorld* hw);

A number of things have changed here. We've added two functions outside of the HelloWorld class that wrap its constructor and destructor. We expose them as extern "C" in order to prevent the compiler from decorating the name of the functions. "Decoration?" you ask. Indeed, in C++ the compiler will mangle the names of the HelloWorld methods such that the CLR cannot identify the proper entry point for a function. You may recognize this issue when you see the EntryPointNotFoundException. Luckily we can help. There is a very useful tool called dumpbin, which provides tons of information about a binary (executable, library etc…). If we want to find what functions are exported via our DLL, we need only do the following:

dumpbin /exports interop101.dll

Looking through the output of this program, we find our target.

?SayThis@HelloWorld@@QAEXPA_W@Z (public: void __thiscall HelloWorld::SayThis(wchar_t *))

We can thus write the following C# code.

[DllImport("interop101.dll", EntryPoint = "HelloWorld_New")]

public static extern IntPtr NewHelloWorld();

[DllImport("interop101.dll", EntryPoint = "HelloWorld_Delete")]

public static extern void DeleteHelloWorld(IntPtr hw);

 

[DllImport("interop101.dll",

            EntryPoint = "?SayThis@HelloWorld@@QAEXPA_W@Z",

            CharSet = CharSet.Unicode,

            CallingConvention = CallingConvention.ThisCall)]

public static extern void SayThis(IntPtr thisptr, String phrase);

 

static void Main(string[] args)

{

    IntPtr hw = NewHelloWorld();

    SayThis(hw, "I'm a C# application using DllImport!");

    DeleteHelloWorld(hw);

}

I suppose this deserves a bit more explanations. The EntryPoint argument to DllImport should be fairly intuitive as we're just guiding the CLR, which cannot auto-magically glean the answer. As for SayThis, I have added two essential arguments to its associated DllImport attribute. The first is specifying the right character set. Since our Message Box expects a Unicode string, the CLR must convert strings appropriately. The second is the calling convention. Since SayThis is an instance method, it contains an implicit argument that represents the "this" pointer of the receiver. In our case, this is a native pointer that we must explicitly pass as an argument.

Voila. Seems easy? Ugly? Weird? Tedious? The least one can say is that this mechanism is quite practical when dealing with flat C-style APIs. This fact is even more true thanks to pinvoke.net, which maintains a repository of mappings so you don't have to do this work in common cases.

In my not so humble opinion though; when it comes performing interop within your code base, this is not a scalable mechanism. In Part 3, we will examine how C++ once again helps make this scenario elegant.

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.

  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;