Register  -  Login
Current Page:  Coding Fun
 Search
Google
 
 
 
 
Location: BlogsCoder's Corner.NET World    
Posted by: gmon 5/19/2007 6:21 PM

In 2002, Microsoft introduced a new programming framework called the .NET Framework. Programs written in any .NET language are compiled to an intermediate code called the Microsoft Intermediate Language (MSIL) and this intermediate code is run on top of something called the Common Language Runtime (CLR). The .NET Framework also includes a large set of prewritten classes called the Framework Class Library.

 

The Microsoft Intermediate Language, CLR, and Framework Class Library are analogous to Java’s Bytecode, Virtual Machine, and Class Library. The main difference between .NET and Java is that .NET supports multiple languages and the interoperability of these languages.

 

The CLR provides services such as garbage collection and manages the execution of intermediate code and hence this type of code is called “Managed Code”. Code produced by Visual Studio 6.0 and earlier is called “Native” or “Unmanaged” Code.

 

Currently, there are several .NET languages supported by Visual Studio 2005

 

  1. C++/CLI

  2. C#

  3. J#

  4. Visual Basic 2005

 

C#, Visual Basic 2005, and J# can produce only managed code. C++/CLI (which is an extension of C++) is the only one that can produce managed, native, and mixed mode code.

 

There is a huge amount of unmanaged legacy code out there. Is there any way to reuse this code in new .NET applications? Luckily, Microsoft has provided features in the .NET framework to facilitate interoperability with unmanaged legacy code.

 

Platform Invoke (P/Invoke) Interop

Today, I’ll take a look at one of the ways we can reuse legacy C/C++ code in new .NET applications. If our legacy C/C++ code is in the form of a DLL and exposes a “C” styled API (no classes), we can use P/Invoke to access this code from the .NET world. P/Invoke is available to all .NET managed languages.

 

Suppose we have a legacy C/C++ native DLL (named MathDLL.dll) that exports the following functions:

 

// MathDLL.h

 

// Returns a + b

extern "C" __declspec(dllexport) double Add(double a, double b);

 

// Returns a - b

extern "C" __declspec(dllexport) double Subtract(double a, double b);

 

// Returns a * b

extern "C" __declspec(dllexport) double Multiply(double a, double b);

 

// Returns a / b

extern "C" __declspec(dllexport) double Divide(double a, double b);


These functions are implemented as follows:

// MathDLL.cpp

#include "MathDLL.h"

 

double Add(double a, double b)

{

return a + b;

}

 

double Subtract(double a, double b)

{

return a - b;

}

 

double Multiply(double a, double b)

{

return a * b;

}

 

double Divide(double a, double b)

{

return a / b;

}


We can use P/Invoke to access these functions from C#. To do this, we need to add a DLLImport attribute to the function declarations as shown in the following C# code:

using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

 

namespace MathDLLUserCSharp

{

class Program

{

// The following are prototypes for functions defined

// in the native DLL

[DllImport("MathDLL.dll")]

public static extern double Add(double a, double b);

 

[DllImport("MathDLL.dll")]

public static extern double Subtract(double a, double b);

 

[DllImport("MathDLL.dll")]

public static extern double Multiply(double a, double b);

 

[DllImport("MathDLL.dll")]

public static extern double Divide(double a, double b);

 

static void Main(string[] args)

{

// Wow, we can use them in C#

Console.WriteLine("1 + 3 = {0}", Add(1, 3));

Console.WriteLine("1 - 3 = {0}", Subtract(1, 3));

Console.WriteLine("1 * 3 = {0}", Multiply(1, 3));

Console.WriteLine("1 / 3 = {0}", Divide(1, 3));

}

}

}


Using P/Invoke from Visual Basic 2005 is very similar. There is only a slight difference in the syntax from the C# version:

Imports System.Runtime.InteropServices

 

Module Module1

 

The following are prototypes for functions defined

in the native DLL

"MathDLL.dll")> _

Public Function Add(ByVal a As Double, ByVal b As Double) As Double

End Function

 

"MathDLL.dll")> _

Public Function Subtract(ByVal a As Double, ByVal b As Double) As Double

End Function

 

"MathDLL.dll")> _

Public Function Multiply(ByVal a As Double, ByVal b As Double) As Double

End Function

 

"MathDLL.dll")> _

Public Function Divide(ByVal a As Double, ByVal b As Double) As Double

End Function

Sub Main()

 

Wow, we can use them in VB.NET

Console.WriteLine("1 + 3 = {0}", Add(1, 3))

Console.WriteLine("1 - 3 = {0}", Subtract(1, 3))

Console.WriteLine("1 * 3 = {0}", Multiply(1, 3))

Console.WriteLine("1 / 3 = {0}", Divide(1, 3))

 

End Sub

 

End Module


For Visual Basic 2005, an alternate syntax for declaring these functions is to use the following VB 6.0 compatible Declare statement (which is actually remapped to the DLLImport method)

 

Declare Auto Function Add Lib "MathDLL.dll" Alias "Add" (ByVal a As Double, b As Double) As Double

 

Finally, using P/Invoke from C++/CLI is very similar to the other 2 .NET languages. Again, there is only a slight difference in the syntax from the C# version:

// MathDLLUserCPP.cpp

#include "stdafx.h"

 

using namespace System;

using namespace System::Runtime::InteropServices;

 

// The following are prototypes for functions defined

// in the native DLL

[DllImport("MathDLL.dll")]

extern double Add(double a, double b);

 

[DllImport("MathDLL.dll")]

extern double Subtract(double a, double b);

 

[DllImport("MathDLL.dll")]

extern double Multiply(double a, double b);

 

[DllImport("MathDLL.dll")]

extern double Divide(double a, double b);

 

int main(array ^args)

{

// Wow, we can use them in managed C++/CLI

Console::WriteLine("1 + 3 = {0}", Add(1, 3));

Console::WriteLine("1 - 3 = {0}", Subtract(1, 3));

Console::WriteLine("1 * 3 = {0}", Multiply(1, 3));

Console::WriteLine("1 / 3 = {0}", Divide(1, 3));

return 0;

}

 

The above was a simple example to give an overview of how using P/Invoke works. In reality, most real world functions would be more complicated. For example, what happens if some of the parameters are more complex than the basic types? What if they were strings? Or perhaps they were structures and classes? For those situations we would need to do some data marshalling. The LLImport attribute accepts optional parameters that can help with some situations involving more complicated parameter types. But when things get really complex, I prefer to use C++ Interop as the way to access legacy C/C++ code.

Permalink |  Trackback
 
 
 
Privacy Statement  |  Terms Of Use  |  Copyright 2007 by GmonWeb