Friday, October 9, 2009

Дружба народов или использование native code библиотек с c#

Так как изобретать велосипед не хотелось, решил организовать поддержку в моем проекте плагинов для Total Commander. Плагины представляют собой динамические библиотеки двоичного кода со списком поддерживаемых функций, определяющих интерфейс плагина. Насчет статического связывания native библиотек с .net кодом написано много (программирование с помощью аттрибутов DllImport), но вот вопрос как загрузить библиотеку во время выполнения и выполнить функцию, которая экспортируется этой библиотекой остается мало освещен. Исправлю этот пробел.

Итак, постановка задачи:
Есть бибилотека someDll.dll, которая является не .net сборкой, а так называемым двоичным кодом(или "родным" (native)). Обычно такие библиотеки написаны на С++ или Delphi. Эта библиотека экспортирует функцию

void libraryFunction(char* text);


Необходимо вызвать эту функцию в проекте .net на этапе выполнения. В данном примере - в проекте на языке C#

Порядок действий.

1. Необходимо описать список win32 api функций, необходимых для работы с библиотеками. Использование этих функций легко организовать при помощи статического связывания при помощи InteropServices(Не забывайте добавлять директиву "using System.Runtime.InteropServices;" вначале файла) и использования аттрибута DllImport. Рекоммендую ресурс PInvoke.net для поиска готовых описаний системных функций, чтобы не ошибится при использовании функций со сложными входными параметрами и возвращаемыми значениями. Если вы используете SharpDevelop, то в контекстном меню редактора есть пункт "Insert PInvoke Signature" и выбрав его и введя в строке поиска необходимую функцию, вы сразу можете вставить готовое описание.


2. Для загрузки библиотеки необходима функция LoadLibrary. Она возвращает дескриптор библиотеки, который необходим для использования ее функций. В качестве параметра используется char[] который вы можете получить, используя метод ToCharArray() у класса string.


3. Необходимо получить адрес функции, которую вы хотите использовать. Используем GetProcAddress, первым параметром указываем дескриптор загруженной библиотеки, который мы получили вызвав LoadLibrary, а вторым параметром указываем название функции, которую хотим вызвать.

Примечание: Название функции содержит только имя функции без скобок, параметров и возвращаемого значения. Иначе функция вернет вам адресс IntPtr.Zero

4. Так как .net считается безопасным кодом, то преобразовать адрес функции в функцию невозможно. Но среда предоставляет возможность безопасно преобразовать адрес в делегат. Нам необходима функция "void libraryFunction(char* text);". Для этого объявим делегат

internal delegate void functionDelegate(char[] text);


Преобразовываем адрес функции в делегат(аналог безопасного варианта указателя на функцию в C++ для среды .net) при помощи метода Marshal.GetDelegateForFunctionPointer

functionDelegate libFunction = (functionDelegate)Marshal.
GetDelegateForFunctionPointer(procID, typeof(
functionDelegate));


5. И теперь мы свободно можем пользоваться нашей функцией при помощи делегата.



6. Не забваем выгрузить библиотеку при помощи FreeLibrary после того, как библиотека стала не нужной.

using System;
using System.Runtime.InteropServices;

class Imports
{
#region external imports

[DllImport("kernel32", SetLastError = true)]
public static extern IntPtr LoadLibrary(string
lpFileName);

[DllImport("kernel32", CharSet = CharSet.Ansi,
ExactSpelling = true, SetLastError = true)]
public internal static extern IntPtr GetProcAddress(
IntPtr hModule, string procName);

[DllImport("kernel32.dll", SetLastError=true)]
static extern bool FreeLibrary(IntPtr hModule);

#endregion
}

class DLL_Loader
{
internal delegate void functionDelegate(char[] text);

public void useFunctionFromDLL()
{
IntPtr libID = Imports.LoadLibrary("someDll.dll");
IntPtr procID = Imports.GetProcAddress(libID,
"libraryFunction");

functionDelegate libFunction = (functionDelegate)
Marshal.GetDelegateForFunctionPointer(procID, typeof(
functionDelegate));

libFunction("my string".ToCharArray());

Imports.FreeLibrary(libID);
}
}

No comments:

Post a Comment