DllImport
With the aid of the DllImport attribute, we can call unmanaged functions, provided they reside in a DLL. For example, the following C++/CLI program calls an unmanaged C function called Hypot that resides in a DLL called Tools.dll:
using namespace System; using namespace System::Runtime::InteropServices; [DllImport("Tools.dll", CallingConvention = CallingConvention::StdCall)] extern double Hypot(double side1, double side2);
The first argument one must pass to the DllImport constructor is the DLL's name, as shown. This attribute has a number of fields, whose values can be set during construction. The one used here is CallingConvention, which has been set to the value StdCall. (This also happens to be the default calling convention.)
int main() { Console::WriteLine("Hypotenuse = {0}", Hypot(3, 4)); Console::WriteLine("Hypotenuse = {0}", Hypot(5, 12)); Console::WriteLine("Hypotenuse = {0}", Hypot(2.34, 6.78)); }
The output produced is:
Hypotenuse = 5 Hypotenuse = 13 Hypotenuse = 7.17244728108893
Here then is the unmanaged C source:
#include <math.h> __declspec(dllexport) double __stdcall Hypot(double side1, double side2) { return sqrt((side1 * side1) + (side2 * side2)); }
Since the default calling convention for C is cdecl, the keyword stdcall has been used. (Alternatively, the default could have been used and the calling convention changed in the C++/CLI code.)
Here are some more examples; these involve calling functions in the Win32 API library and the Visual C++ Standard C library:
using namespace System; using namespace System::Text; using namespace System::Runtime::InteropServices; // BOOL SetCurrentDirectory(LPCTSTR pstrDirName); [DllImport("Kernel32.dll", CharSet = CharSet::Unicode)] extern bool SetCurrentDirectory(String^ pstrDirName); // DWORD GetCurrentDirectory(DWORD dwLen, LPTSTR pstrDirName); [DllImport("Kernel32.dll", CharSet = CharSet::Unicode)] extern int GetCurrentDirectory(int length, StringBuilder^ pstrDirName);
Functions that expect a C-style string (that is, a null-terminated array of char or wchar_t), and that do not modify that string, have their corresponding argument declared as String^. If the Unicode (16-bit character) version is wanted, that is the character set selected; if the ANSI (8-bit character) version is wanted, that is selected.
Functions that expect a C-style string and that do modify that string have their corresponding argument declared as StringBuilder^.
Since all the functions in the Win32 API library use the StdCall calling convention, and that is the default for the DllImport attribute, the calling convention need not be stated.
// UINT GetSystemDirectory(LPTSTR lpBuffer, UINT uSize) [DllImport("Kernel32.dll", CharSet = CharSet::Auto)] extern unsigned int GetSystemDirectory(StringBuilder^ sysDirBuffer, unsigned int size); // BOOL GetUserName(LPTSTR lpBuffer, LPDWORD nSize); [DllImport("Advapi32.dll", CharSet = CharSet::Auto)] extern bool GetUserName(StringBuilder^ userNameBuffer, unsigned int *size); // int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) [DllImport("User32.dll")] extern int MessageBox(IntPtr hWnd, String^ text, String^ caption, unsigned int type);
The managed type IntPtr is used to hold a generic address.
// int printf(const char *format [, argument]... ); [DllImport("msvcrt.dll", CharSet = CharSet::Ansi, CallingConvention = CallingConvention::Cdecl)] extern int printf(String^ format, int i); [DllImport("msvcrt.dll", CharSet = CharSet::Ansi, CallingConvention = CallingConvention::Cdecl)] extern int printf(String^ format, double d);
Functions, like printf, having a variable number of arguments, can be imported via a number of overloads.
Since all the functions in the Standard C library use the Cdecl calling convention, and that is not the default for the DllImport attribute, the calling convention is specified.
// int wprintf(const wchar_t *format [, argument]... ); [DllImport("msvcrt.dll", CharSet = CharSet::Unicode, CallingConvention = CallingConvention::Cdecl)] extern int wprintf(String^ format, int i); [DllImport("msvcrt.dll", CharSet = CharSet::Unicode, CallingConvention = CallingConvention::Cdecl)] extern int wprintf(String^ format, double d);
As defined, printf is intended to deal with single-byte characters, while wprintf is intended for wide characters; that is, Unicode.
// size_t strlen(const char *str); [DllImport("msvcrt.dll", CharSet = CharSet::Ansi, CallingConvention = CallingConvention::Cdecl)] extern unsigned int strlen(String^ str); int main() { StringBuilder^ curDirBuffer = gcnew StringBuilder(256); int result = GetCurrentDirectory(curDirBuffer->Capacity, curDirBuffer); Console::WriteLine("Current path is >{0}<", curDirBuffer); StringBuilder^ sysDirBuffer = gcnew StringBuilder(256); GetSystemDirectory(sysDirBuffer, (unsigned int)sysDirBuffer->Capacity); Console::WriteLine("The system directory is >{0}<", sysDirBuffer); StringBuilder^ userNameBuffer = gcnew StringBuilder(128); unsigned int size = (unsigned int)userNameBuffer->Capacity; GetUserName(userNameBuffer, &size); Console::WriteLine("The username is >{0}<", userNameBuffer); result = MessageBox(IntPtr::Zero, "MyMessage", "MyTitle", 1);" Console::WriteLine("Call result: {0}", result.ToString() ); SetCurrentDirectory("e:\\temp"); result = GetCurrentDirectory(curDirBuffer->Capacity, curDirBuffer); Console::WriteLine("Current path is >{0}<", curDirBuffer); printf("printf: %d\n", 123); printf("printf: %f\n", 98.76); wprintf("wprintf: %d\n", 123); wprintf("wprintf: %f\n", 98.76); Console::WriteLine("String length = {0}", strlen("Managed C++").ToString()); }