Вызов managed кода из unmanaged

15 ноября 2011 г.

В данной статье будет рассматриваться вызов управляемого C# — кода(.Net) из неуправляемого С — кода. Как-то раз на работе дали проект, точнее даже не сам проект, а только его часть. Сам же проект состоял из двух частей: функционал, написанный на С (unmanaged code) и интерфейсная часть, написанная на C# (managed code). Моей задачей было написать интерфейс и связать его с функционалом. Далее в этой статье managed code будет называться верхним уровнем, unmanaged – нижним. Как известно, для обращения к нижнему уровню с верхнего в C# используется механизм P/Invoke(Platform Invoke).

Для этого нижний уровень оборачивается в Dll (то есть все функции нижнего уровня делаются экспортируемыми) и вызывается сверху с помощью атрибута DllImport. Для тех, кто незнаком с данным механизмом — В ходе выполнения задания передо мной встала проблема – на нижнем уровне находилась функция обратного вызова, которая должна была уведомить верхний уровень об удачном или неудачном завершении функции. Для решения проблемы нужно было либо вызвать с нижнего уровня верхний уровень либо придумать какой-либо механизм, позволяющий узнать момент вызова функции верхнего уровня (например, при помощи события). Поиск в интернете по теме вызова managed кода из unmanaged не принес должных плодов.

Тем не менее было решено попробовать обойтись малой кровью и не изобретать велосипед. Для упрощения понимания был создан новый солюшн, включающий два проекта: проект низкого(CallC) и проект высокого уровня(SharpForCall). Итак, у нас есть пустой проект C#( Console Application) и кем-то написанный проект на С (у меня изначально конечно же был только h-file, берем сразу проект для простоты). Тип проекта на С – Dll, которая естественно должна лежать рядом с нашим экзешником, полученным в C#. В проекте есть *.cpp файл следующего содержания: /** Обратный уведомляющий вызов */ typedef VOID (__stdcall * pResultCallBack)( int nStatus );


__declspec(dllexport) int FuncC(pResultCallBack pfResult, PVOID pContext) { // // здесь что-то делаем //
/* перед выходом из функции уведомляем высший уровень(C#) о результате */ pfResult(1);
return 1; }

Еще раз поясню смысл того, что нужно сделать. Экспортируемая функция здесь(FuncC) будет импортируемой на стороне C#, которая, предположим, вызовется при нажатии пользователем какой-нибудь кнопки (не будем забывать, что главной задачей является связь интерфейса с функционалом). Эта функция (импортируемая на стороне C#) вызовет, естественно, функцию FuncC в данном файле *.cpp (см. выше), которая после выполнения должна сообщить результат выполнения назад в C# при помощи вызова функции pResultCallBack. На стороне верхнего уровня функция pResultCallBack (в нашем случае FuncCSharp, см ниже) будет анализировать результат выполнения функции FuncC и в зависимости от переданного ей значения выполнять определенные действия (например, при возврате кода состояния, сообщающего о неудачном вызове, можно повторить вызов и т.д.). Вообще данная идея может использоваться для управления одной машиной (хостом) другой машиной. Приступим к реализации. Во-первых, заходим в настройки С-шного проекта в Configuration Properties->General->Output Directory и пишем путь к папке с экзешником проекта C#.  Во-вторых, не забываем зайти в Project Dependencies проекта C# и поставить там галочку рядом с С-проектом.  Далее, создаем класс Import.cs, в котором описываем импортируемую функцию при помощи механизма P/Invoke.

using System; using System.Runtime.InteropServices;// не забываем подключить
namespace SharpForCall { class Import { [DllImport(«CallC.dll», CallingConvention = CallingConvention.Cdecl)] public static extern int FuncC( [MarshalAs(UnmanagedType.FunctionPtr)] ResultCallBack pfResult, IntPtr pContext); } }
PVOID заменяем на IntPtr, а указатель на функцию pResultCallBack на делегат ResultCallBack, который описан в файле Program.cs следующим образом:
using System;
namespace SharpForCall { public delegate void ResultCallBack(Int32 nStatus);
class Program { static void Main(string[] args) { ResultCallBack result = new ResultCallBack(FuncCSharp);
IntPtr pContext = IntPtr.Zero;
/* Вызов функции из Dll */ int nRes = Import.FuncC(result, pContext); }
/** Функция анализа обратного вызова. * Вызывается из unmanaged кода. */ public static void FuncCSharp(Int32 nStatus) { if ( 1 == nStatus ) { //бла бла бла } if ( 2 == nStatus ) { //бла бла бла } // и так далее
return; } } }

Теперь, запустив программу и пройдясь по ней по шагам (для того чтобы войти в unmanaged код нужно установить чекбокс в свойствах проекта -> Debug->Enable unmanaged code debugging), мы увидим, что сначала верхний уровень вызывает нижний, передавая ему (нижнему кровню) делегат, а нижний – по окончании выполнения функции FuncC вызывает верхний (функцию FuncCSharp) по тому самому «делегату» и передает ему результат выполнения функции (в данном случае «1»). Далее функция анализирует полученный код состояния и возвращает управление на нижний уровень, оттуда управление передается на верхний уровень. Если при выполнении программа выдает ексепшн следующего содержания: image то допишите на стороне С в определении колбека __stdcall. Если и это не помогло, то на стороне C# в классе Import нужно дописать CallingConvention = CallingConvention.Cdecl при вызове атрибута DllImport. Это все нужно для того, чтобы вернуть стек в исходное состояние. Теперь все работает. Мы только что совершили то, что многие считают невозможным – вызвали managed код из unmanaged.

Теги: рубрика C#