Обработка управляемых делегатов в неуправляемом коде - PullRequest
4 голосов
/ 03 октября 2008

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

У меня есть управляемая библиотека, которая упаковывает неуправляемую библиотеку в стиле Си. Функциональность библиотеки в стиле 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, который передается из какой-то клиент моей управляемой библиотеки.

Ответы [ 3 ]

2 голосов
/ 03 октября 2008

Если я правильно понимаю проблему, вам нужно объявить неуправляемую функцию обратного вызова в вашей сборке 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, либо, возможно, обратный вызов получает переменную, предоставленную пользователем, которую вы можете использовать.

2 голосов
/ 03 октября 2008

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

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

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

0 голосов
/ 03 ноября 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);
...