Обработка управляемых делегатов в неуправляемом коде

Я знаю, что могу заставить это работать технически, но я хотел бы реализовать максимально чистое решение. Вот ситуация:

У меня есть управляемая библиотека, которая обертывает неуправляемую библиотеку C-стиля. Функциональность библиотеки в стиле C, которую я сейчас обертываю, выполняет некоторую обработку, включающую список строк. Клиентский код библиотеки может предоставить делегата, так что во время обработки списка, если встречается «недопустимый» сценарий, библиотека может выполнить обратный вызов клиенту через этот делегат и позволить ему выбрать стратегию для использования (сгенерировать исключение, заменить недопустимые символы и т. д.)

В идеале я хотел бы иметь весь управляемый C++, изолированный в одной функции, а затем иметь возможность вызывать отдельную функцию, которая принимает только неуправляемые параметры, чтобы весь собственный C++ и неуправляемый код были изолированы в этой одной точке. Предоставление механизма обратного вызова для этого неуправляемого кода оказалось для меня камнем преткновения.


#pragma managed
public delegate string InvalidStringFilter(int lineNumber, string text);
...
public IList<Result> DoListProcessing(IList<string> listToProcess, InvalidStringFilter filter)
{
  // Managed code goes here, translate parameters etc.
}

#pragma unmanaged
// This should be the only function that actually touches the C-library directly
std::vector<NativeResult> ProcessList(std::vector<char*> list, ?? callback);

В этом фрагменте я хочу сохранить весь доступ к C-библиотеке в ProcessList, но во время обработки ему нужно будет выполнять обратные вызовы, и этот обратный вызов предоставляется в виде делегата InvalidStringFilter, который передается от какого-либо клиента моя управляемая библиотека.


person Reddog    schedule 03.10.2008    source источник


Ответы (3)


.NET может автоматически преобразовывать делегата в указатель на функцию, если он объявлен правильно. Есть два предостережения

  1. Функция C должна быть построена STDCALL
  2. Указатель на функцию не считается ссылкой на объект, поэтому вы должны организовать сохранение ссылки, чтобы базовый объект не собирал мусор.

http://www.codeproject.com/KB/mcpp/FuncPtrDelegate.aspx?display=Print

person Lou Franco    schedule 03.10.2008
comment
Спасибо за второй комментарий. Это, наконец, объясняет, почему я продолжаю получать SEH в случайное время. - person galets; 25.05.2012
comment
Мне повезло, что кто-то сказал мне это в первый день моего знакомства с C#, так что я всегда это знал. Он знал, что я собираюсь написать мост C#/C++. - person Lou Franco; 25.05.2012

Если я правильно понимаю проблему, вам нужно объявить неуправляемую функцию обратного вызова в вашей сборке C++/CLI, которая действует как мост между вашей библиотекой C и управляемым делегатом.


#pragma managed
public delegate string InvalidStringFilter(int lineNumber, string text);

...
static InvalidStringFilter sFilter;

public IList<Result> DoListProcessing(IList<string> listToProcess, InvalidStringFilter filter)
{
  // Managed code goes here, translate parameters etc.
  SFilter = filter;
}

#pragma unmanaged

void StringCallback(???)
{
  sFilter(????);
}

// This should be the only function that actually touches the C-library directly
std::vector<NativeResult> ProcessList(std::vector<char*> list, StringCallback);

Как написано, этот код явно не потокобезопасен. Если вам нужна безопасность потоков, то потребуется какой-то другой механизм для поиска правильного управляемого делегата в обратном вызове, либо ThreadStatic, либо, возможно, обратный вызов получает предоставленную пользователем переменную, которую вы могли бы использовать.

person Rob Walker    schedule 03.10.2008
comment
Таким образом, по сути, у меня был бы указатель неуправляемой функции, вызывающий неуправляемую функцию, тогда у меня была бы эта неуправляемая функция, вызывающая делегата, который был сохранен как переменная-член в моем управляемом классе? - person Reddog; 04.10.2008
comment
По сути, да. Вам нужно выяснить, как функция StringCallback получает доступ к управляемому делегату. Фрагмент выше просто делает его глобальной переменной. StringCallback также может выполнять перевод между типами, например, std::string и String^ - person Rob Walker; 04.10.2008

Вы хотите сделать что-то вроде этого:

typedef void (__stdcall *w_InvalidStringFilter) (int lineNumber, string message);

GCHandle handle = GCHandle::Alloc(InvalidStringFilter);
w_InvalidStringFilter callback = 
  static_cast<w_InvalidStringFilter>(
    Marshal::GetFunctionPointerForDelegate(InvalidStringFilter).ToPointer()
  );

std::vector<NativeResult> res = ProcessList(list, callback);
person Rasmus Faber    schedule 03.11.2008