C# Language
Interoperability
Поиск…
замечания
Работа с API Win32 с использованием C #
Windows предоставляет множество функций в виде Win32 API. Используя эти API, вы можете выполнять прямую работу в окнах, что повышает производительность вашего приложения. Источник
Windows предоставляет широкий спектр API. Чтобы получить информацию о различных API, вы можете проверять сайты, подобные pinvoke .
Функция импорта из неуправляемой библиотеки C ++
Вот пример того, как импортировать функцию, определенную в неуправляемой DLL C ++. В исходном коде C ++ для «myDLL.dll» определена функция add
:
extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
return a + b;
}
Затем он может быть включен в программу C # следующим образом:
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));
}
}
См. « Соглашения о __stdcall
extern "C"
и « __stdcall
C ++» для объяснения причин, по которым необходимы extern "C"
и __stdcall
.
Поиск динамической библиотеки
При первом вызове метода extern программа C # будет искать и загружать соответствующую DLL. Для получения дополнительной информации о том, где искать, найти DLL, и как вы можете влиять на места поиска, см. Этот вопрос в stackoverflow .
Простой код для публикации класса для com
using System;
using System.Runtime.InteropServices;
namespace ComLibrary
{
[ComVisible(true)]
public interface IMainType
{
int GetInt();
void StartTime();
int StopTime();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MainType : IMainType
{
private Stopwatch stopWatch;
public int GetInt()
{
return 0;
}
public void StartTime()
{
stopWatch= new Stopwatch();
stopWatch.Start();
}
public int StopTime()
{
return (int)stopWatch.ElapsedMilliseconds;
}
}
}
C ++ name mangling
Компиляторы C ++ кодируют дополнительную информацию в именах экспортируемых функций, таких как типы аргументов, чтобы сделать возможными перегрузки с различными аргументами. Этот процесс называется изменением имени . Это приводит к проблемам с импортированием функций в C # (и взаимодействием с другими языками вообще), поскольку имя функции int add(int a, int b)
больше не add
, это может быть ?add@@YAHHH@Z
, _add@8
или что-либо еще, в зависимости от компилятора и соглашения о вызове.
Существует несколько способов решения проблемы смены имени:
Экспортирование функций с использованием
extern "C"
для переключения на внешнюю связь C, которая использует сглаживание имени C:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Имя функции все равно будет
_add@8
(_add@8
), но сStdCall
компилятора C # распознаетсяStdCall
+extern "C"
.Указание имен экспортируемых функций в
myDLL.def
определения модуляmyDLL.def
:EXPORTS add
int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
В этом случае имя функции будет чисто
add
.Импортирование измененного имени. Для просмотра искаженного имени вам понадобится некоторое средство просмотра DLL, и вы можете указать его явно:
__declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
Вызовы
Существует несколько соглашений о вызовах, указывающих, кто (вызывающий или вызываемый) выдает аргументы из стека, как передаются аргументы и в каком порядке. C ++ использует Cdecl
вызове Cdecl
по умолчанию, но C # ожидает StdCall
, который обычно используется Windows API. Вам нужно изменить одно или другое:
Изменение соглашения о
StdCall
вStdCall
в C ++:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Или, измените соглашение о
Cdecl
наCdecl
в C #:extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
Если вы хотите использовать функцию с Cdecl
вызове Cdecl
и Cdecl
именем, ваш код будет выглядеть так:
__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?add@@YAHHH@Z")]
thiscall ( __thiscall ) в основном используется в функциях, входящих в класс.
Когда функция использует thiscall ( __thiscall ), указатель на класс передается в качестве первого параметра.
Динамическая загрузка и выгрузка неуправляемых библиотек DLL
При использовании атрибута DllImport
вы должны знать правильное имя DLL и имя метода во время компиляции . Если вы хотите быть более гибкими и решать во время выполнения, какие DLL и методы загружать, вы можете использовать методы Windows API LoadLibrary()
, GetProcAddress()
и FreeLibrary()
. Это может быть полезно, если используемая библиотека зависит от условий выполнения.
Указатель, возвращаемый GetProcAddress()
может быть переведен в делегат, используя Marshal.GetDelegateForFunctionPointer()
.
Следующий пример кода демонстрирует это с помощью myDLL.dll
из предыдущих примеров:
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);
}
}
Работа с ошибками Win32
При использовании методов interop вы можете использовать GetLastError API для получения дополнительной информации о ваших вызовах API.
Атрибут DllImport Атрибут SetLastError
SetLastError = верно
Указывает, что вызываемый вызовет вызов SetLastError (функция Win32 API).
SetLastError = ложь
Указывает, что вызываемый вызов не будет вызывать SetLastError (функция Win32 API), поэтому вы не получите информацию об ошибке.
Если SetLastError не установлен, он устанавливается в значение false (значение по умолчанию).
Вы можете получить код ошибки с помощью метода Marshal.GetLastWin32Error:
Пример:
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);
Если вы пытаетесь открыть мьютекс, которого не существует, GetLastError вернет ERROR_FILE_NOT_FOUND .
var lastErrorCode = Marshal.GetLastWin32Error();
if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
//Deal with error
}
Коды ошибок системы можно найти здесь:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
API GetLastError
Существует собственный API GetLastError, который вы также можете использовать:
[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
- При вызове Win32 API из управляемого кода вы всегда должны использовать Marshal.GetLastWin32Error .
Вот почему:
Между вашим вызовом Win32, который устанавливает ошибку (вызывает SetLastError), среда CLR может вызывать другие вызовы Win32, которые также могут вызвать SetLastError , это поведение может переопределить ваше значение ошибки. В этом случае, если вы вызываете GetLastError, вы можете получить недопустимую ошибку.
Установка SetLastError = true , убедитесь, что CLR извлекает код ошибки до того, как он выполнит другие вызовы Win32.
Связанный объект
GC (Garbage Collector) несет ответственность за очистку нашего мусора.
В то время как GC очищает наш мусор, он удаляет неиспользуемые объекты из управляемой кучи, которые вызывают фрагментацию кучи. Когда GC выполняется с удалением, он выполняет сжатие кучи (defragmintation), которое включает в себя перемещение объектов в куче.
Поскольку GC не является детерминированным, когда передается ссылка на управляемый объект / указатель на собственный код, GC может ударить в любое время, если это происходит сразу после вызова Inerop, есть очень хорошая вероятность того, что объект (эта ссылка передана на native) будет перемещаться по управляемой куче - в результате мы получаем неверную ссылку на управляемую сторону.
В этом случае вы должны привязать объект до передачи его в собственный код.
Связанный объект
Прикрепленный объект - объект, который не разрешен для перемещения по GC.
Gc Pinned Handle
Вы можете создать контактный объект, используя метод Gc.Alloc
GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned);
- Получение привязанного GCHandle к управляемому объекту маркирует определенный объект как тот, который не может быть перемещен GC , пока не освободит дескриптор
Пример:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void EnterCriticalSection(IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void LeaveCriticalSection(IntPtr ptr);
public void EnterCriticalSection(CRITICAL_SECTION section)
{
try
{
GCHandle handle = GCHandle.Alloc(section, GCHandleType.Pinned);
EnterCriticalSection(handle.AddrOfPinnedObject());
//Do Some Critical Work
LeaveCriticalSection(handle.AddrOfPinnedObject());
}
finaly
{
handle.Free()
}
}
Меры предосторожности
- При фиксации (особенно больших) объектов старайтесь как можно быстрее освободить закрепленный GcHandle, так как он прерывает дефрагментацию кучи.
- Если вы забудете освободить GcHandle, ничего не будет. Сделайте это в разделе безопасного кода (например, finaly)
Чтение структур с маршалом
Класс маршала содержит функцию PtrToStructure , эта функция дает нам возможность считывания структур неуправляемым указателем.
Функция PtrToStructure получила много перегрузок, но все они имеют одно и то же намерение.
Общая структура PtrToStructure :
public static T PtrToStructure<T>(IntPtr ptr);
T - тип структуры.
ptr - указатель на неуправляемый блок памяти.
Пример:
NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);
- Если вы работаете с управляемыми объектами при чтении собственных структур, не забудьте прикрепить свой объект :)
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;
}