Поиск…


замечания

Работа с 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;
    }


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow