Продолжаем маршалировать. На этот раз будет рассмотрен способ вызова С-шных функций из C# без использования P/Invoke( [DllImport] ). А если быть до конца точным, то [DllImport] использовать все же придется, но только один раз. По существу, данная статья является статьей о маршалинге делегатов в указатели на функции и обратно.
Итак, приступим.
Первое, с чего нужно начать, это нужно вспомнить, что когда мы от чего-то отказываемся (в данном случае [DllImport]), мы заменяем это чем-то другим. Так и здесь. Отказавшись от P/Invoke (в C#) и экспорта из Dll (в C), приходится объявлять структуру с указателями на функцию (в C) и структуру с делегатами (в C#).
Предположим, у вас есть заголовочный файл и файл с реализацией с кодом следующего вида (неважно, откуда и каким образом они получены):
Файл NativeCode.h
Файл NativeCode.cpp
Примечание 1: в данном примере «__stdcall» — уже дописывается читателем для восстановления состояния стека.
Примечание 2: количество указателей на функции, объявленных в качестве полей структуры (SInterface) может быть различным, то есть структура может содержать сколь угодно разное количество указателей на функции.
Как видно из примера, у нас есть указатель (pFuncInterface) на функцию (FuncInterface), которая принимает int и возвращает int, структура с двумя полями (SInterface), одним из которых является указатель на функцию, и функция заполнения структуры по полям (CreateInterface).
Нашей задачей является написание на C# кода взаимодействия с данной структурой. Первое, что приходит в голову, это экспорт всех функций на стороне С и вызов их через [DllImport] на стороне C#, а в структуре оставить только поля с данными (исключить указатели на функции). Но код менять не хочется (к тому же, если он вообще не ваш, его, скорее всего, менять просто и нельзя).
В этом случае следует поступить следующим образом.
Как обычно, создаем проект Win32 (Console Application) и указываем, что он будет компиляться в Dll, а также создаем проект C# — Console Application. В Dll добавляем два наших исходных файла и функцию заполнения структуры по полям делаем экспортируемой (это единственная функция, для которой потребуется вызов P/Invoke). Итак, у нас есть Dll с одной лишь экспортируемой функцией CreateInterface().
Недостатком данного метода является то, что на стороне C# приходится объявлять точно такую же структуру, в которой указатели на функции заменяются делегатами (приходится объявлять делегат на каждую функцию), то есть на стороне C#, по существу, происходит дублирование кода *.h файла (в данном случае NativeCode.h). Тем не менее, проделывая это, а также [DllImport] для CreateInterface(), получаем:
Теперь остается дописать в двух местах «__stdcall» и вуаля, все работает. Вызывая CreateInterface() из Dll, происходит заполнение структуры SInterface указателями на функции (в данном случае одним), которые на стороне C# маршалируются в делегаты. Теперь можно использовать полученный делегат для вызова функции из неуправляемого кода напрямую, минуя механизм P/Invoke.
Результат программы, как и следовало ожидать:
PS: данный способ является лишь альтернативной заменой вызова unmanaged кода с помощью P/Invoke.
Итак, приступим.
Первое, с чего нужно начать, это нужно вспомнить, что когда мы от чего-то отказываемся (в данном случае [DllImport]), мы заменяем это чем-то другим. Так и здесь. Отказавшись от P/Invoke (в C#) и экспорта из Dll (в C), приходится объявлять структуру с указателями на функцию (в C) и структуру с делегатами (в C#).
Предположим, у вас есть заголовочный файл и файл с реализацией с кодом следующего вида (неважно, откуда и каким образом они получены):
Файл NativeCode.h
/* Функция, вызываемая из структуры */
typedef
int
(__stdcall * pFuncInterface)(
__in int nStatus
);
typedef struct _Interface
{
// здесь объявляем указатели на все нужные нам функции
pFuncInterface m_pfInterface;
// также могут содержаться и данные
DWORD m_dwData;
} SInterface, *PSInterface;
/* Функция заполнения структуры */
__declspec(dllexport)
int
CreateInterface(
__inout PSInterface pInterface
);
Файл NativeCode.cpp
int __stdcall
FuncInterface(
__in int nStatus)
{
nStatus = 5;
return 1;
}
/* Экспортируемая функция */
int
CreateInterface(
__inout PSInterface pInterface)
{
// тут заполнение структуры указателями на функции
pInterface->m_pfInterface = FuncInterface;
// какие-либо данные
pInterface->m_dwData = 5;
return pInterface->m_dwData;
}
Примечание 1: в данном примере «__stdcall» — уже дописывается читателем для восстановления состояния стека.
Примечание 2: количество указателей на функции, объявленных в качестве полей структуры (SInterface) может быть различным, то есть структура может содержать сколь угодно разное количество указателей на функции.
Как видно из примера, у нас есть указатель (pFuncInterface) на функцию (FuncInterface), которая принимает int и возвращает int, структура с двумя полями (SInterface), одним из которых является указатель на функцию, и функция заполнения структуры по полям (CreateInterface).
Нашей задачей является написание на C# кода взаимодействия с данной структурой. Первое, что приходит в голову, это экспорт всех функций на стороне С и вызов их через [DllImport] на стороне C#, а в структуре оставить только поля с данными (исключить указатели на функции). Но код менять не хочется (к тому же, если он вообще не ваш, его, скорее всего, менять просто и нельзя).
В этом случае следует поступить следующим образом.
Как обычно, создаем проект Win32 (Console Application) и указываем, что он будет компиляться в Dll, а также создаем проект C# — Console Application. В Dll добавляем два наших исходных файла и функцию заполнения структуры по полям делаем экспортируемой (это единственная функция, для которой потребуется вызов P/Invoke). Итак, у нас есть Dll с одной лишь экспортируемой функцией CreateInterface().
Недостатком данного метода является то, что на стороне C# приходится объявлять точно такую же структуру, в которой указатели на функции заменяются делегатами (приходится объявлять делегат на каждую функцию), то есть на стороне C#, по существу, происходит дублирование кода *.h файла (в данном случае NativeCode.h). Тем не менее, проделывая это, а также [DllImport] для CreateInterface(), получаем:
using System;
using System.Runtime.InteropServices;
namespace SharpCode
{
/****************************************************/
// Дублирование кода из *.h файла
public delegate int pInterface(int nStatus);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct SInterface
{
public pInterface m_pfpInterface;
public UInt32 m_dwData;
}
/****************************************************/
class Program
{
private static SInterface stInterface = new SInterface();
[DllImport("NativeDll", CallingConvention = CallingConvention.Cdecl)]
public static extern int
CreateInterface(
ref SInterface pInterface
);
static void Main(string[] args)
{
CreateInterface(ref stInterface);
// теперь наша структура заполнена и можно вызывать функции
// из unmanaged code без использования P/Invoke
int nRes = stInterface.m_pfpInterface(1);
Console.WriteLine("Result = {0}", nRes);
}
}
}
Теперь остается дописать в двух местах «__stdcall» и вуаля, все работает. Вызывая CreateInterface() из Dll, происходит заполнение структуры SInterface указателями на функции (в данном случае одним), которые на стороне C# маршалируются в делегаты. Теперь можно использовать полученный делегат для вызова функции из неуправляемого кода напрямую, минуя механизм P/Invoke.
Результат программы, как и следовало ожидать:
PS: данный способ является лишь альтернативной заменой вызова unmanaged кода с помощью P/Invoke.