Как стать автором
Обновить

Вызов unmanaged code из managed без P/Invoke

Время на прочтение3 мин
Количество просмотров3.7K
Продолжаем маршалировать. На этот раз будет рассмотрен способ вызова С-шных функций из C# без использования P/Invoke( [DllImport] ). А если быть до конца точным, то [DllImport] использовать все же придется, но только один раз. По существу, данная статья является статьей о маршалинге делегатов в указатели на функции и обратно.
Итак, приступим.

Первое, с чего нужно начать, это нужно вспомнить, что когда мы от чего-то отказываемся (в данном случае [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.

Результат программы, как и следовало ожидать:

image

PS: данный способ является лишь альтернативной заменой вызова unmanaged кода с помощью P/Invoke.
Теги:
Хабы:
+20
Комментарии12

Публикации

Истории

Работа

.NET разработчик
74 вакансии

Ближайшие события