В данной статье будет рассматриваться вызов управляемого C# — кода(.Net) из неуправляемого С — кода.
Как-то раз на работе дали проект, точнее даже не сам проект, а только его часть. Сам же проект состоял из двух частей: функционал, написанный на С (unmanaged code) и интерфейсная часть, написанная на C# (managed code). Моей задачей было написать интерфейс и связать его с функционалом.
Далее в этой статье managed code будет называться верхним уровнем, unmanaged – нижним.
Как известно, для обращения к нижнему уровню с верхнего в C# используется механизм P/Invoke(Platform Invoke). Для этого нижний уровень оборачивается в Dll (то есть все функции нижнего уровня делаются экспортируемыми) и вызывается сверху с помощью атрибута DllImport. Для тех, кто незнаком с данным механизмом — msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx
В ходе выполнения задания передо мной встала проблема – на нижнем уровне находилась функция обратного вызова, которая должна была уведомить верхний уровень об удачном или неудачном завершении функции. Для решения проблемы нужно было либо вызвать с нижнего уровня верхний уровень либо придумать какой-либо механизм, позволяющий узнать момент вызова функции верхнего уровня (например, при помощи события). Поиск в интернете по теме вызова managed кода из unmanaged не принес должных плодов. Тем не менее было решено попробовать обойтись малой кровью и не изобретать велосипед.
Для упрощения понимания был создан новый солюшн, включающий два проекта: проект низкого(CallC) и проект высокого уровня(SharpForCall).
Итак, у нас есть пустой проект C#( Console Application) и кем-то написанный проект на С (у меня изначально конечно же был только h-file, берем сразу проект для простоты). Тип проекта на С – Dll, которая естественно должна лежать рядом с нашим экзешником, полученным в C#. В проекте есть *.cpp файл следующего содержания:
Еще раз поясню смысл того, что нужно сделать. Экспортируемая функция здесь(FuncC) будет импортируемой на стороне C#, которая, предположим, вызовется при нажатии пользователем какой-нибудь кнопки (не будем забывать, что главной задачей является связь интерфейса с функционалом). Эта функция (импортируемая на стороне C#) вызовет, естественно, функцию FuncC в данном файле *.cpp (см. выше), которая после выполнения должна сообщить результат выполнения назад в C# при помощи вызова функции pResultCallBack. На стороне верхнего уровня функция pResultCallBack (в нашем случае FuncCSharp, см ниже) будет анализировать результат выполнения функции FuncC и в зависимости от переданного ей значения выполнять определенные действия (например, при возврате кода состояния, сообщающего о неудачном вызове, можно повторить вызов и т.д.). Вообще данная идея может использоваться для управления одной машиной (хостом) другой машиной.
Приступим к реализации.
Во-первых, заходим в настройки С-шного проекта в Configuration Properties->General->Output Directory и пишем путь к папке с экзешником проекта C#.
Во-вторых, не забываем зайти в Project Dependencies проекта C# и поставить там галочку рядом с С-проектом.
Далее, создаем класс Import.cs, в котором описываем импортируемую функцию при помощи механизма P/Invoke.
PVOID заменяем на IntPtr, а указатель на функцию pResultCallBack на делегат ResultCallBack, который описан в файле Program.cs следующим образом:
Теперь, запустив программу и пройдясь по ней по шагам (для того чтобы войти в unmanaged код нужно установить чекбокс в свойствах проекта -> Debug->Enable unmanaged code debugging), мы увидим, что сначала верхний уровень вызывает нижний, передавая ему (нижнему уровню) делегат, а нижний – по окончании выполнения функции FuncC вызывает верхний (функцию FuncCSharp) по тому самому «делегату» и передает ему результат выполнения функции (в данном случае «1»). Далее функция анализирует полученный код состояния и возвращает управление на нижний уровень, оттуда управление передается на верхний уровень. Если при выполнении программа выдает ексепшн следующего содержания:
, то дописываем на стороне С в определении колбека __stdcall.
Если и это не помогло, то на стороне C# в классе Import нужно дописать CallingConvention = CallingConvention.Cdecl при вызове атрибута DllImport. Это все нужно для того, чтобы вернуть стек в исходное состояние.
Теперь все работает. Мы только что совершили то, что многие считают невозможным – вызвали managed код из unmanaged.
PS: Думаю, кому-нибудь пригодится. Жду ваших комментариев…
Как-то раз на работе дали проект, точнее даже не сам проект, а только его часть. Сам же проект состоял из двух частей: функционал, написанный на С (unmanaged code) и интерфейсная часть, написанная на C# (managed code). Моей задачей было написать интерфейс и связать его с функционалом.
Далее в этой статье managed code будет называться верхним уровнем, unmanaged – нижним.
Как известно, для обращения к нижнему уровню с верхнего в C# используется механизм P/Invoke(Platform Invoke). Для этого нижний уровень оборачивается в Dll (то есть все функции нижнего уровня делаются экспортируемыми) и вызывается сверху с помощью атрибута DllImport. Для тех, кто незнаком с данным механизмом — msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx
В ходе выполнения задания передо мной встала проблема – на нижнем уровне находилась функция обратного вызова, которая должна была уведомить верхний уровень об удачном или неудачном завершении функции. Для решения проблемы нужно было либо вызвать с нижнего уровня верхний уровень либо придумать какой-либо механизм, позволяющий узнать момент вызова функции верхнего уровня (например, при помощи события). Поиск в интернете по теме вызова 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»). Далее функция анализирует полученный код состояния и возвращает управление на нижний уровень, оттуда управление передается на верхний уровень. Если при выполнении программа выдает ексепшн следующего содержания:
, то дописываем на стороне С в определении колбека __stdcall.
Если и это не помогло, то на стороне C# в классе Import нужно дописать CallingConvention = CallingConvention.Cdecl при вызове атрибута DllImport. Это все нужно для того, чтобы вернуть стек в исходное состояние.
Теперь все работает. Мы только что совершили то, что многие считают невозможным – вызвали managed код из unmanaged.
PS: Думаю, кому-нибудь пригодится. Жду ваших комментариев…