Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
57
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

Chapter 155: Interoperability

Section 155.1: Import function from unmanaged C++ DLL

Here is an example of how to import a function that is defined in an unmanaged C++ DLL. In the C++ source code for "myDLL.dll", the function add is defined:

extern "C" __declspec(dllexport) int __stdcall add(int a, int b)

{

return a + b;

}

Then it can be included into a C# program as follows:

class Program

{

//This line will import the C++ method.

//The name specified in the DllImport attribute must be the DLL name.

//The names of parameters are unimportant, but the types must be correct.

[DllImport("myDLL.dll")]

private static extern int add(int left, int right);

static void Main(string[] args)

{

//The extern method can be called just as any other C# method.

Console.WriteLine(add(1, 2));

}

}

See Calling conventions and C++ name mangling for explanations about why extern "C" and __stdcall are necessary.

Finding the dynamic library

When the extern method is first invoked the C# program will search for and load the appropriate DLL. For more information about where is searched to find the DLL, and how you can influence the search locations see this stackoverflow question.

Section 155.2: Calling conventions

There're several conventions of calling functions, specifying who (caller or callee) pops arguments from the stack, how arguments are passed and in what order. C++ uses Cdecl calling convention by default, but C# expects StdCall, which is usually used by Windows API. You need to change one or the other:

Change calling convention to StdCall in C++:

extern "C" __declspec(dllexport) int __stdcall add(int a, int b)

[DllImport("myDLL.dll")]

Or, change calling convention to Cdecl in C#:

extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b) [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]

GoalKicker.com – C# Notes for Professionals

743

If you want to use a function with Cdecl calling convention and a mangled name, your code will look like this:

__declspec(dllexport) int add(int a, int b)

[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,

EntryPoint = "?add@@YAHHH@Z")]

thiscall(__thiscall) is mainly used in functions that are members of a class.

When a function uses thiscall(__thiscall) , a pointer to the class is passed down as the first parameter.

Section 155.3: C++ name mangling

C++ compilers encode additional information in the names of exported functions, such as argument types, to make overloads with di erent arguments possible. This process is called name mangling. This causes problems with importing functions in C# (and interop with other languages in general), as the name of int add(int a, int b) function is no longer add, it can be ?add@@YAHHH@Z, _add@8 or anything else, depending on the compiler and the calling convention.

There're several ways of solving the problem of name mangling:

Exporting functions using extern "C" to switch to C external linkage which uses C name mangling:

extern "C" __declspec(dllexport) int __stdcall add(int a, int b)

[DllImport("myDLL.dll")]

Function name will still be mangled (_add@8), but StdCall+extern "C" name mangling is recognized by C# compiler.

Specifying exported function names in myDLL.def module definition file:

EXPORTS add

int __stdcall add(int a, int b)

[DllImport("myDLL.dll")]

The function name will be pure add in this case.

Importing mangled name. You'll need some DLL viewer to see the mangled name, then you can specify it explicitly:

__declspec(dllexport) int __stdcall add(int a, int b)

[DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]

Section 155.4: Dynamic loading and unloading of unmanaged DLLs

When using the DllImport attribute you have to know the correct dll and method name at compile time. If you want to be more flexible and decide at runtime which dll and methods to load, you can use the Windows API methods LoadLibrary(), GetProcAddress() and FreeLibrary(). This can be helpful if the library to use depends on runtime

GoalKicker.com – C# Notes for Professionals

744

conditions.

The pointer returned by GetProcAddress() can be casted into a delegate using

Marshal.GetDelegateForFunctionPointer().

The following code sample demonstrates this with the myDLL.dll from the previous examples:

class Program

{

//import necessary API as shown in other examples

[DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr LoadLibrary(string lib); [DllImport("kernel32.dll", SetLastError = true)] public static extern void FreeLibrary(IntPtr module); [DllImport("kernel32.dll", SetLastError = true)]

public static extern IntPtr GetProcAddress(IntPtr module, string proc);

//declare a delegate with the required signature

private delegate int AddDelegate(int a, int b);

private static void Main()

{

// load the dll

IntPtr module = LoadLibrary("myDLL.dll"); if (module == IntPtr.Zero) // error handling

{

Console.WriteLine($"Could not load library: {Marshal.GetLastWin32Error()}"); return;

}

// get a "pointer" to the method

IntPtr method = GetProcAddress(module, "add"); if (method == IntPtr.Zero) // error handling

{

Console.WriteLine($"Could not load method: {Marshal.GetLastWin32Error()}"); FreeLibrary(module); // unload library

return;

}

// convert "pointer" to delegate

AddDelegate add = (AddDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(AddDelegate));

// use function

int result = add(750, 300);

// unload library

FreeLibrary(module);

}

}

Section 155.5: Reading structures with Marshal

Marshal class contains a function named PtrToStructure, this function gives us the ability of reading structures by an unmanaged pointer.

PtrToStructure function got many overloads, but they all have the same intention.

Generic PtrToStructure:

GoalKicker.com – C# Notes for Professionals

745

public static T PtrToStructure<T>(IntPtr ptr);

T - structure type.

ptr - A pointer to an unmanaged block of memory.

Example:

NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);

If you dealing with managed objects while reading native structures, don't forget to pin your object :)

T Read<T>(byte[] buffer)

{

T result = default(T);

var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);

try

{

result = Marshal.PtrToStructure<T>(gch.AddrOfPinnedObject());

}

finally

{

gch.Free();

}

return result;

}

Section 155.6: Dealing with Win32 Errors

When using interop methods, you can use GetLastError API to get additional information on you API calls.

DllImport Attribute SetLastError Attribute

SetLastError=true

Indicates that the callee will call SetLastError (Win32 API function).

SetLastError=false

Indicates that the callee will not call SetLastError (Win32 API function), therefore you will not get an error information.

When SetLastError isn't set, it is set to false (Default value).

You can obtain the error code using Marshal.GetLastWin32Error Method:

Example:

[DllImport("kernel32.dll", SetLastError=true)]

public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);

If you trying to open mutex which does not exist, GetLastError will return ERROR_FILE_NOT_FOUND.

var lastErrorCode = Marshal.GetLastWin32Error();

GoalKicker.com – C# Notes for Professionals

746