Abstract
C++ managed code introduced us a new string
type, namely System.String. You can imagine though, that some conversion
functions are needed to work with this new string type when mixing managed and
unmanaged code in your project.
Introduction
Managed code runs under the .NET runtime Execution
Engine, which gives you the advantage of garbage collection, JIT,
etc. Currently, as a programmer you can choose between C#, C++ managed
extensions and VB.NET to write your managed code (there are also .NET
versions of FORTRAN and COBOL, however, these are not widely available at the
time of writing - ed. ). However, in some cases, you do want to
mix your managed code with unmanaged code : either you still want to continue
using your current source code base and plug it in .NET, maybe you want to
build .NET interfaces around your existing code, or you decide to write some
routines unmanaged because of performance issues.
Mixing managed and unmanaged code
/CLR
If you want to mix managed and unmanaged
code in the same sources, your code should be compiled with the /CLR compiler
option. This means that - by default � all your functions are managed. For
compilation: members of managed classes as well as functions who use managed
data types only compile as managed.
#pragma managed
#pragma unmanaged
Using the managed and unmanaged pragmas enables
function-level control for compiling functions managed or unmanaged.
Strings
Unmanaged C++ applications use string types composed of
ASCII/UNICODE characters ( char*
, wchar_t*
, std::string
,
CString
).
Managed objects can�t handle these types, therefore a new string type
System.String is introduced.
In the MSDN documentation you will find
that :
"Regular C++ wide-character string
literals (prefixed by L) and managed string literals (prefixed by S)
can be used interchangeably where String types are expected. However,
the reverse is not true. Managed string literals can not be used where C++
string types are expected."
So, in functions where a char*
argument is
expected, a conversion from String
to char*
will need to happen.
In the System::Runtime::InteropServices
namespace (assembly:
mscorlib.dll ), a
Marshal class is available to do such conversions.
This Marshal class takes care of unmanaged memory allocation, copying
unmanaged memory blocks, and managed to unmanaged type conversions.
For example, consider this piece of code
(written in C++) :
#pragma unmanaged
#include "Windows.h"
class CUnmanagedClass
{
public:
void ShowMessageBox(char* szMessage)
{
::MessageBox(NULL, szMessage , "CUnmanagedClass", MB_OK);
}
};
#pragma managed
#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
__gc class CManagedClass
{
public:
void ShowMessage(System::String* strMessage)
{
System::Console::WriteLine(strMessage);
}
};
int main(void)
{
String* strMessage = S"Hello world";
CManagedClass* pCManagedClass = new CManagedClass();
pCManagedClass->ShowMessage(strMessage);
char* szMessage = (char*)Marshal::StringToHGlobalAnsi(strMessage);
CUnmanagedClass cUnmanagedClass; cUnmanagedClass.ShowMessageBox(szMessage);
Marshal::FreeHGlobal((int)szMessage);
return 0;
}
The unmanaged class CUnManagedClass
, contains one
method which expects a char*
as argument. If you a want to call that
method ( ShowMessageBox
) from a managed
piece of code, you would need to do a conversion like this:
char* szMessage = (char*)Marshal::StringToHGlobalAnsi(strMessage);
And freeing the memory again with FreeHGlobal:
Marshal::FreeHGlobal((int)szMessage);
In the sample, I used StringToHGlobalAnsi
to do
the conversion. StringToHGlobalAnsi
copies the contents of a managed
String object into native heap and converts it into ASCII. The result of
StringToHGlobalAnsi
is an
integer value, which should be cast to a char pointer.
Data marshaling when making calls between
managed and unmanaged code when using P/Invoke in C#
In C#, unmanaged methods are called by using the Runtime's
Platform Invocation Service (P/Invoke). Your data also needs to be marshaled
then : the marshalling attributes can be specified in the dllimport statement.
Consider the following lines of code (written in C#):
[DllImport("user32.dll")]
public static extern int MessageBoxA(int h,
[MarshalAs(UnmanagedType.LPStr)]string m,
[MarshalAs(UnmanagedType.LPStr)]string c,
int type);
public static int Main(string args [])
{
MessageBoxA(0, "Hello World!", "My Message Box", 0);
return 0;
}
The function MessageBoxA
expects 2 char pointer
arguments (these are non-supported types in a managed environment). By
specifying the attribute MarshalAs(UnmanagedType.LPStr)
, the
runtime's marshaling service will take care of transferring the data
of our strings from the managed environment to the unmanaged environment.
Conclusion
Mixing managed and unmanaged code can be
very powerful, and it gives us � programmers - a lot
more freedom to decide on how to code. However, we should be aware that mixing
these technologies introduces some extra difficulties and should be taken care
off from the beginning, when starting to design your .NET applications.