(How)
C^# You Are - 12 August 2007
P/Invoke is the topic this week. Due to imminent work on the next Kraken release
and a major update to the site it is short.
- How do you pass an array to an unmanaged function using P/Invoke? Answer
If the array will not be modified within the unmanaged function then you can pass
the array just as you would to a regular managed function. The marshaller
handles the details of determining how many elements to pass to the unmanaged function.
Any changes made to the array inside the unmanaged function will be lost.
//C++
__declspec(dllexport) void Foo ( int[] elements, int count ) { ... }
//C#
private static extern void Foo ( int[] elements, int cout );
- You define the following unmanaged function in your C++ DLL but you get an error
when you try to call it from C# saying it can not be found. What is wrong? Answer
//C++
__declspec(dllexport) int Foo ( void );
//C#
private static extern int Foo ( );
When dealing with other languages it is important to take the name mangling into
account. Pascal-based calling conventions prepend symbols while C++ prepends
symbols and hashes the parameters. C includes an underscore on the front.
P/Invoke can handle C mangling along with a few variations. However it can
not handle C++. Therefore you should export functions you want to call from
managed code as C functions using extern "C". You can then
call the functions in managed code without worrying about name mangling.
//C++
extern "C" __declspec(dllexport) int Foo ( void );
//C#
private static extern int Foo ( );
- You do not have access to the source for the unmanaged C++ function you want to
call. How can you call it from managed code? Answer
When you do not have the source or when you can not control the calling convention
used then you need to resort to the EntryPoint property of the
DllImport attribute. The EntryPoint property
allows you to identify the mangled function name. This property allows you
to use a nice, clean name for managed code while still using the mangled name from
the unmanaged library.
//C++
__declspec(dllexport)
int void Foo ( ); //Assume it is mangled to _@_Foo_Hash
//C#
[DllImport("foo.dll", EntryPoint="_@_Foo_Hash")]
private static extern int Foo ( );
- How do you pass a fixed sized string to an unmanaged function such that the unmanaged
function can populate it? Answer
This is common in the Win32 APIs. A fixed size string (for our purposes) is
either a character array that has a fixed size or one that must be of at least a
minimum size. For example most Win32 API functions dealing with paths require
a path string to be at least MAX_PATH (approximately 256) in size. This, again
for our purposes, is a fixed size array.
The problem is that you must pass a buffer large enough to hold all the characters
that the function requires. Normally with strings the marshaller will only
pass the minimum number of characters to represent the entire string. In the
above case though you need a fixed size. StringBuilder comes to the rescue. StringBuilder allows you to specify the size
of the string in advance. You can then use the builder as a buffer to pass
to the unmanaged function. Upon return you can use ToString
to get the final string.
The only downside to this approach is that you must create the builder in advance
with the correct size. This is one of the reasons why wrapping P/Invoke calls
is highly recommended.
//C++
extern "C" __declspec(dllexport) void GetProgramPath ( char* path ); //Assumes
standard MAX_PATH size
//C#
public string GetProgramPath ( )
{
StringBuilder bldr = new StringBuilder(256); //MAX_PATH - approximately
as it seems to change with each OS it seems
GetProgramPath(bldr);
return bldr.ToString();
}
private static extern void GetProgramPath ( StringBuilder path );
- How do you pass a PoD to an unmanaged function and allow its value to be changed? Answer
Fortunately this works very similar to managed code. Just add the ref
(or out) modifier to the parameter and the marshaller will pass
the data back from the unmanaged function.
//C
__declspec(dllexport) void Multiply ( int* value );
//C++
extern "C" __declspec(dllexport) void Multiply ( int& value );
//C#
private static extern void Multiply ( ref int value );
- The following P/Invoke call returns the wrong data. What is wrong with it? Answer
//C++
__declspec(dllexport) long Multiply ( long value, short multiple ) { return value
* multiple; }
//C#
private static extern long Multiply ( long value, short multiple );
The problem lies in the length of the data types. This is a common problem
whenever dealing with P/Invoke situations. A long in most
languages, like C++, is only 32-bits. In .NET it is 64-bits. The marshaller
passes a 64-bit numeric followed by a 16-bit numeric to the unmanaged function.
However in unmanaged code it only looks at the first two 32-bit values. When
returning it pushes a 32-bit value on the stack but the marshaller pops off 64-bits.
Chaos ensues.
Be sure to verify that the types you are using match the lengths defined by the
unmanaged function.
//C++
extern "C" __declspec(dllexport) long Multiply ( long value, short multiple );
//C#
private static extern int Multiply ( int value, short multiple );