Calling Native Functions from Managed Code
The common language runtime provides Platform Invocation Services, or PInvoke, that enables managed code to call C-style functions in native dynamic-linked libraries (DLLs). The same data marshaling is used as for COM interoperability with the runtime and for the "It Just Works," or IJW, mechanism.
For more information, see:
The samples in this section just illustrate how PInvoke can be used. PInvoke can simplify customized data marshaling because you provide marshaling information declaratively in attributes instead of writing procedural marshaling code.
PInvoke and the DllImport Attribute
The following example shows the use of PInvoke in a Visual C++ program. The native function puts is defined in msvcrt.dll. The DllImport attribute is used for the declaration of puts.
// platform_invocation_services.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
[DllImport("msvcrt", CharSet=CharSet::Ansi)]
extern "C" int puts(String ^);
int main() {
String ^ pStr = "Hello World!";
puts(pStr);
}
The following sample is equivalent to the previous sample, but uses IJW.
// platform_invocation_services_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
#include <stdio.h>
int main() {
String ^ pStr = "Hello World!";
char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer();
puts(pChars);
Marshal::FreeHGlobal((IntPtr)pChars);
}
Advantages of IJW
-
There is no need to write DLLImport attribute declarations for the unmanaged APIs the program uses. Just include the header file and link with the import library.
-
The IJW mechanism is slightly faster (for example, the IJW stubs do not need to check for the need to pin or copy data items since that is done explicitly by the developer).
-
It clearly illustrates performance issues. In this case, the fact that you are translating from a Unicode string to an ANSI string and that you have an attendant memory allocation and deallocation. In this case, a developer writing the code using IJW would realize that calling _putws and using PtrToStringChars would be better for performance.
-
If you need to call many unmanaged APIs using the same data, marshaling it once up front and passing the marshaled copy around is much more efficient than re-marshaling every time.
Disadvantages of IJW
-
Marshaling needs to be specified explicitly in code rather than by attributes (which in many cases like this one have suitable defaults).
-
The marshaling code is inline, where it is more invasive in the flow of the application logic.
-
Since the explicit marshaling APIs return IntPtr types for 32-bit to 64-bit portability, extra ToPointer calls need to be used.
Like many places, the specific method exposed by C++ is the more efficient, explicit method, at the cost of some additional complexity.
If the application uses mainly unmanaged data types or if it calls more unmanaged APIs than .NET Framework APIs, using the IJW feature will generally be preferable. To call an occasional unmanaged API in a mostly managed application, the choice is more subtle.
Marshaling Arguments
With PInvoke, no marshaling is required between managed and C++ native primitive types with the same form. For example, no marshaling is required between Int32 and int, and Double and double.
Marshaling is required for types that do not have the same form. This includes char, string, and struct types. The following table shows the mappings used by the marshaler for various types.
| wtypes.h | Visual C++ | Visual C++ with /clr | Common Language Runtime |
|---|---|---|---|
| HANDLE | void * | void * | IntPtr, UIntPtr |
| BYTE | unsigned char | unsigned char | Byte |
| SHORT | short | short | Int16 |
| WORD | unsigned short | unsigned short | UInt16 |
| INT | int | int | Int32 |
| UINT | unsigned int | unsigned int | UInt32 |
| LONG | long | long | Int32 |
| BOOL | long | bool | Boolean |
| DWORD | unsigned long | unsigned long | UInt32 |
| ULONG | unsigned long | unsigned long | UInt32 |
| CHAR | char | char | Char |
| LPCSTR | char * | String ^ [in], StringBuilder ^ [in, out] | String ^ [in], StringBuilder ^ [in, out] |
| LPCSTR | const char * | String ^ | String |
| LPWSTR | wchar_t * | String ^ [in], StringBuilder ^ [in, out] | String ^ [in], StringBuilder ^ [in, out] |
| LPCWSTR | const wchar_t * | String ^ | String |
| FLOAT | float | float | Single |
| DOUBLE | double | double | Double |
The marshaler automatically pins memory allocated on the runtime heap if its address is passed to an unmanaged function. Pinning prevents the garbage collector from moving the allocated block of memory during compaction.
In the example shown earlier in this topic, the CharSet parameter of DllImport specifies how managed Strings should be marshaled; in this case, they should be marshaled to ANSI strings for the native side.
Marshaling information for individual arguments of a native function can be specified using the MarshalAs attribute. There are several choices for marshaling a String * argument: BStr, ANSIBStr, TBStr, LPStr, LPWStr, and LPTStr. The default is LPStr.
// platform_invocation_services_3.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
[DllImport("msvcrt", EntryPoint="puts")]
extern "C" int puts([MarshalAs(UnmanagedType::LPWStr)] String ^);
int main() {
String ^ pStr = "Hello World!";
puts(pStr);
}
In this example, the string is marshaled as a double-byte Unicode character string, LPWStr. The output is just the first letter of Hello World! because the second byte of the marshaled string is null, and puts interprets this as the end-of-string marker.
The MarshalAs attribute is in the System::Runtime::InteropServices namespace. The attribute can be used with other data types such as arrays.
PInvoke with Windows APIs
PInvoke is also convenient for calling functions in Windows.
In this example, a Visual C++ program interoperates with the MessageBox function that is part of the Win32 API.
// platform_invocation_services_4.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HWND;
[DllImport("user32", CharSet=CharSet::Ansi)]
extern "C" int MessageBox(HWND hWnd, String ^ pText, String ^ pCaption, unsigned int uType);
int main() {
String ^ pText = "Hello World! ";
String ^ pCaption = "PInvoke Test";
MessageBox(0, pText, pCaption, 0);
}
The output is a message box with the title PInvoke Test containing the text Hello World!.
The marshaling information is also used by PInvoke to look up functions in the DLL. In user32.dll there is in fact no MessageBox function, but CharSet=CharSet::Ansi enables PInvoke to use MessageBoxA, the ANSI version, instead of MessageBoxW, which is the Unicode version. In general, using Unicode versions of unmanaged APIs should be preferred because that eliminates the translation overhead from the native Unicode format of .NET Framework string objects to ANSI.
When Not to Use PInvoke
Using PInvoke is not suitable for all C-style functions in DLLs. For example, suppose there is a function MakeSpecial in mylib.dll declared as follows.
char * MakeSpecial(char * pszString);
If we use PInvoke in a Visual C++ application, we might write something similar to:
[DllImport("mylib")]
extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String ^);
The difficulty here is that we cannot delete the memory for the unmanaged string returned by MakeSpecial. Other functions called via PInvoke return a pointer to an internal buffer that does not need to be deallocated by the user. In this case, using the IJW feature is the obvious choice.
Limitations of PInvoke
You cannot return the same exact pointer from a native function that you took as a parameter. If a native function returns the pointer that has been marshaled to it by PInvoke, memory corruption and exceptions may ensue.
__declspec(dllexport)
char* fstringA(char* param) {
return param;
}
The following sample exhibits this problem, and even though the program may seem to give the correct output, the output is coming from memory that had been freed.
// platform_invocation_services_5.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
#include <limits.h>
ref struct MyPInvokeWrap {
public:
[ DllImport("user32.dll", EntryPoint = "CharLower", CharSet = CharSet::Ansi) ]
static String^ CharLower([In, Out] String ^);
};
int main() {
String ^ strout = "AabCc";
Console::WriteLine(strout);
strout = MyPInvokeWrap::CharLower(strout);
Console::WriteLine(strout);
}
Performance Considerations
PInvoke has an overhead of between 10 and 30 x86 instructions per call. In addition to this fixed cost, marshaling creates additional overhead. There is no marshaling cost between blittable types that have the same representation in managed and unmanaged code. For example, there is no cost to translate between int and Int32.
For higher performance, it may be necessary to have fewer PInvoke calls that marshal as much data as possible, rather than have more calls that marshal less data per call.
See Also
I want to describe a bug in the Windows 7 - 64 Bit operating system that results in:
"An attempt was made to load a program with an incorrect format. (Exception 0x8007000B)
System.BadImageFormatException
when using PInvoke in a C# application via
[DllImport("MyDll.dll", EntryPoint="xyz", SetLastError=true)
or that results in Error 193 (ERROR_BAD_EXE_FORMAT) when using LoadLibrary().
If you get this error it normally means that a 64 Bit application tries to load a 32 Bit Dll.
This is what you find everywhere when googling.
But not always - as in my case:
I observed this on Windows 7 - 64 Bit with a C# application compiled on VS2005 as "AnyCpu".
And now comes the interesting part:
This error occurred when loading a 64 Bit MFC Dll (also compiled on VS2005) into this process!
That's incredible, isn't it ?
A BadImageFormat exception !IS! even possible when loading a 64 Bit Dll into a 64 Bit process due to a bug in Windows 7 - 64 Bit!
I wasted 2 entire days of my life investigating this stuff and here comes the result for all those, who have the same problem:
_________________________________________________________________
FIRST:
------
How can you be SURE if the process you are running is a 64 Bit process ?
1.)
You see in Taskmanager a *32 behind all 32 Bit processes and nothing behind all 64 Bit processes.
2.)
But I recommend to install Process Explorer from www.SysInternals.com
ATTENTION: The strange thing is that Process Explorer shows Exe's compiled as "AnyCpu" as if they were 32 Bit!
That means that the EXE is marked as 32 Bit. (I suppose they have the 32 Bit PE header but internally run as 64 Bit)
But you see that all DLLs that this process has loaded like Kernel32.dll are 64 Bit DLLs and this is the clear sign that this process is running as 64 Bit.
So don't care about what the Exe says in Process Explorer!
In the lower pane of Process Explorer you can see all the loaded DLLs with their path and if they are 32 Bit or 64 Bit.
I recommend before you start to analyze your process: First study the differences in Process Explorer between:
C:\Windows\System32\Calc.exe and
C:\Windows\SysWow64\Calc.exe
SECOND:
-------
How can you be !SURE! that the Dll you want to load is really a 64 Bit Dll ?
Install DepdendencyWalker from www.SysInternals.com and it will tell you immediately if a Dll is compiled as 64 Bit or not.
___________________________________________________________________
Back to the problem:
In my case I compiled the Exe and the DLL both on my own and so I knew that
the EXE is "AnyCpu",
the DLL is "x64".
So why did I get the BadImageFormat Excpetion then?
I used another tool: Process Monitor also from www.SysInternals.com to study what the app is doing when starting up.
Immediately after LoadLibary() or PInvoke I saw that Windows was opening a ComCtl32.dll in a folder named
C:\Windows\winsxs\x86_microsoft-windows-comctl32-........
And after that the exception was thrown.
That's the real hammer!
How is it possible that a 64 Bit Process even looks into any folder named X86_... ???
The 64 Bit Dlls are in folders named
C:\Windows\winsxs\amd64_......
Now I found the cause of the BadImageFormat exception:
Not MY Dll had the bad image format!
My Dll depends on ComCtl32.dll and Windwos has so few intelligency that it searches for a depency in the folder of a 32 Bit Dll!
That's really a big fat Windows bug!
It should be obvious that all depencies of a 64 Bit Dll MUST be searched in one of the Amd64 folders, but NOT in the X86 folders.
But that's only one of the bugs!
The next bug is the wrong error message.
The error leads into the wrong direction and made me waste 2 entire days.
The error made me think that my Dll has the wrong Exe format.
But it was a Dll that Windows searched in the wrong folder that obviously had the wrong Exe format!
Now how did I solve the problem:
My Dll needed a Manifest file.
VS2005 automatically embeds a Manifest.
But it is completely useless:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
</assembly>
It simply says nothing.
I found this very good article about manifests:
http://msdn.microsoft.com/en-us/library/bb773175(VS.85).aspx
After reading it and after following the links in this article you will know that there are 3 ways to add a manifest:
1.) As a separate file (usefull if you don't have the sourcecode of the Dll)
2.) As embedded manifest file (RT_MANIFEST)
3.) Via a #pragma comment linker which is the easiest and fastset way on VS2005 or higher.
I did the latter and now it works !
Finally!
I hope that Mircosoft fixes this heavy bug:
A 64 Bit application must never ever search Dlls in SideBySide folders (SxS) starting with X86.... not even if a manifest is missing or wrong or incomplete!
Simply NEVER!
A 64 Bit application must only search in the SxS folders starting with Amd64...
The other topic is the error handling of Microsoft products that always has been a mess.
Error messages are misleading or even suppressed.
The correct error in this case would be:
"Missing manifest for MyDll.dll"
Elmü
- 6/2/2011
- ElmueSoft