Export (0) Print
Expand All

Using the DllImport Attribute

Visual Studio .NET 2003

This topic demonstrates common usage of the DllImport attribute. The first section discusses the benefits of using DllImport to make calls to native code from a managed application. The second section focuses on the aspects of marshaling and the DllImport attribute.

Calling Unmanaged Code from a Managed Application

The DllImport attribute is very useful when reusing existing unmanaged code in a managed application. For instance, your managed application might need to make calls to the unmanaged WIN32 API.

This common scenario is demonstrated in the following code sample where the MessageBox (located in User32.lib) is called:

#using <mscorlib.dll>
using namespace System::Runtime::InteropServices; 
// for DllImportAttribute

namespace SysWin32
{
   [DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)]
   int MessageBox(void* hWnd, wchar_t* lpText, wchar_t* lpCaption, 
                  unsigned int uType);
}

int main( )
{
   SysWin32::MessageBox( 0, L"Hello world!", L"Greetings", 0 );
}

The main point of interest is the line of code containing DllImport. Based on the parameter values, this line of code tells the compiler to declare a function residing in the User32.dll and to treat all strings appearing in the signature (such as parameters or the return value) like Unicode strings. If the EntryPoint argument is missing, the default value is the name of the function. In addition, because the CharSet argument specifies Unicode, the common language runtime will first look for a function called MessageBoxW (W because of the Unicode specification). If the runtime does not find this function, it will then look for MessageBox and the corresponding decorated name depending on the calling convention. The only supported conventions are __cdecl and __stdcall.

When calling a function contained in a user-defined DLL, it is necessary to precede the DLL function declaration with extern "C", like this:

// The function declaration in SampleDLL.h file
extern "C" SAMPLEDLL_API int fnSampleDLL(void);

For more information on other supported parameter values, see DllImport.

Marshaling Nonstructured Parameters from Managed to Unmanaged

In addition to using the previous method, you can use another method that marshals the managed parameters (from the managed application) to unmanaged parameters (in the unmanaged DLL).

The following code sample demonstrates the marshaling technique:

#using <mscorlib.dll>
using namespace System; // To bring System::String in
using namespace System::Runtime::InteropServices; 
// for DllImportAttribute
namespace SysWin32
{
   [DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)]
   Int32 MessageBox( Int32 hWnd, String* lpText, String* lpCaption, 
                     UInt32 uType );
}

int main( )
{
   SysWin32::MessageBox(0, S"Hello world!", S"Greetings", 0);
}

When the actual call is made, all parameter strings are automatically converted to wchar_t*, because of the value of the CharSet parameter. Similarly, any parameter types of Int32 are converted to unmanaged int and parameter types of UInt32 are converted to unmanaged unsigned int.

The following table provides a guide to the result of converting unmanaged and managed contexts:

Unmanaged code Managed Extensions for C++
int Int32
unsigned int UInt32
short Int16
char* String* (CharSet = Ansi) for [in] parameters, Text::StringBuilder* for [out] parameters or return values.
wchar_t* String* (CharSet = Unicode) for [in] parameters, Text::StringBuilder* for [out] parameters or return values.
Function pointer (callbacks)
Limitation: The function pointer must have __stdcall calling convention because this is the only type supported by DllImport.
Delegate type
Array (for example, wchar_t*[])
Limitation: The CharSet argument only applies to the root type of the function parameters. Therefore, String* __gc[] is marshaled as wchar_t* [] regardless of the value of CharSet.
Managed array of the corresponding type (such as String*__gc[])

Marshaling Structured Types from Unmanaged to Managed

In addition to simple types, the runtime provides a mechanism for marshaling simple structures from a managed context to an unmanaged one. Simple structures contain no internal data member pointers, members of structured types, or other elements.

As an example, this topic shows how to call a function in a native DLL that has the following signature:

#include <stdio.h>
struct S
{
   char* str;
   int n;
};

int __cdecl func( struct S* p )
{
   printf( "%s\n", p->str );
   return p->n;
}

To create a managed wrapper of this function, apply the StructLayout attribute to the calling class. This attribute determines how the structure is organized when it is marshaled. To ensure the structure is organized using the traditional C format, sequential layout is specified (LayoutKind::Sequential). The resulting code is as follows:

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

// CharSet = Ansi(Unicode) means that everything that is a string 
// in this structure should be marshaled as Ansi(Unicode) 
// strings
[StructLayout( LayoutKind::Sequential, CharSet=Ansi )]
__gc class MS // To be compatible with the type in the native 
// code, this structure should have the members laid out in
// the same order as those in the native struct
{
public:
   String* m_str;
   Int32 m_n;
};

[DllImport("some.dll")]
Int32 func( MS* ptr );
int main( )
{
   MS* p = new MS;
   p->m_str = S"Hello native!";
   p->m_n = 7;
   Console::WriteLine(func(p)); // Should print 7
}

You can also use the __nogc keyword in the managed application, ensuring that no marshaling takes place:

#include <stdlib.h>
#include <string.h>
#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
__nogc class UMS
{
public:
   char* m_str;
   int m_n;
};
[DllImport("some.dll")]
Int32 func( UMS* ptr );
int main( )
{
   UMS* p = new UMS;
   p->m_str = strdup( "Hello native!" );
   p->m_n = 7;
   Console::WriteLine(func(p)); // Should print 7
   free( p->m_str );
   delete p;
}

The second scenario is:

#include <stdio.h>
struct S
{
   wchar_t* str;
   int n;
};
int __cdecl func( struct S p )
{
   printf( "%S\n", p.str );
   return p.n;
}

Observe that the parameter is passed by value. To wrap this call in the managed application, use values instead of __gc types. The resulting code is as follows:

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
[StructLayout( LayoutKind::Sequential, CharSet=Unicode )]
__value class VS
{
public:
   String* m_str;
   Int32 m_n;
};
[DllImport( "some.dll" )]
Int32 func( VS ptr );
int main( )
{
   VS v;
   v.m_str = S"Hello native!";
   v.m_n = 7;
   Console::WriteLine(func(v)); // should print 7 also
}

See Also

Attributes Walkthroughs

Show:
© 2014 Microsoft