2016-01-15 2 views
4

Используя Microsoft Visual C# 2010, я недавно заметил, что вы можете передавать объекты по ссылке на неуправляемый код. Поэтому я поставил себе задачу написать некоторый неуправляемый код, который преобразует символ C++ в строку C#, используя обратный вызов управляемого кода. Я сделал две попытки.Насколько безопасен ref при использовании с небезопасным кодом?

Попытка 1: Вызвать неуправляемую функцию, в которой хранится параметр ref. Затем, как только эта функция вернется к управляемому коду, вызовите другую неуправляемую функцию, которая вызывает функцию обратного вызова, которая преобразует char * в управляемую строку.

C++ 
typedef void (_stdcall* CallbackFunc)(void* ManagedString, char* UnmanagedString); 

CallbackFunc UnmanagedToManaged = 0; 
void* ManagedString = 0; 

extern "C" __declspec(dllexport) void __stdcall StoreCallback(CallbackFunc X) { 
    UnmanagedToManaged = X; 
} 
extern "C" __declspec(dllexport) void __stdcall StoreManagedStringRef(void* X) { 
    ManagedString = X; 
} 
extern "C" __declspec(dllexport) void __stdcall CallCallback() { 
    UnmanagedToManaged(ManagedString, "This is an unmanaged string produced by unmanaged code"); 
} 

C# 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void StoreCallback(CallbackFunc X); 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void StoreManagedStringRef(ref string X); 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void CallCallback(); 

[UnmanagedFunctionPointer(CallingConvention.StdCall)] 
public delegate void CallbackFunc(ref string Managed, IntPtr Native); 

static void Main(string[] args) { 
    string a = "This string should be replaced"; 

    StoreCallback(UnmanagedToManaged); 
    StoreManagedStringRef(ref a); 
    CallCallback(); 
} 

static void UnmanagedToManaged(ref string Managed, IntPtr Unmanaged) { 
    Managed = Marshal.PtrToStringAnsi(Unmanaged); 
} 

Попытка 2: Pass строка исх неуправляемой функции, которая передает строку реф на управляемом обратный вызов.

C++ 
typedef void (_stdcall* CallbackFunc)(void* ManagedString, char* UnmanagedString); 

CallbackFunc UnmanagedToManaged = 0; 

extern "C" __declspec(dllexport) void __stdcall StoreCallback(CallbackFunc X) { 
    UnmanagedToManaged = X; 
} 
extern "C" __declspec(dllexport) void __stdcall DoEverything(void* X) { 
    UnmanagedToManaged(X, "This is an unmanaged string produced by unmanaged code"); 
} 

C# 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void StoreCallback(CallbackFunc X); 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void DoEverything(ref string X); 

[UnmanagedFunctionPointer(CallingConvention.StdCall)] 
public delegate void CallbackFunc(ref string Managed, IntPtr Unmanaged); 

static void Main(string[] args) { 
    string a = "This string should be replaced"; 

    StoreCallback(UnmanagedToManaged); 
    DoEverything(ref a); 
} 

static void UnmanagedToManaged(ref string Managed, IntPtr Unmanaged) { 
    Managed = Marshal.PtrToStringAnsi(Unmanaged); 
} 

Попытка 1 не работает, но попытка 2 делает. В попытке 1 кажется, что как только неуправляемый код возвращается после сохранения ref, ref становится недействительным. Почему это происходит?

Учитывая результаты попытки 1, у меня есть сомнения, что попытка 2 будет надежно работать. Итак, насколько безопасна ссылка на неуправляемую сторону кода при использовании с неуправляемым кодом? Или, другими словами, что не будет работать в неуправляемом коде при использовании ref?

Вещи, которые я хотел бы знать, являются следующие:

Что именно происходит, когда объекты передаются с помощью реф неуправляемого кода?

Гарантирует ли это, что объекты будут оставаться в их текущем положении в памяти, в то время как ref используется в неуправляемом коде?

Каковы ограничения ref (что я не могу сделать с ref) в неуправляемом коде?

+0

Небезопасный код называется «небезопасным» по какой-либо причине ... он небезопасен. :) – Almo

ответ

1

Полное обсуждение того, как п/ссылаться на работы выходит за рамки надлежащего объема в Stack Overflow Q & А. Но кратко:

ни в одном из ваших примеров вы действительно передавая адрес управляемой переменной к неуправляемый код. Уровень p/invoke включает логику маршалинга, которая преобразует ваши управляемые данные в нечто, пригодное для использования неуправляемым кодом, а затем переводит назад, когда возвращается неуправляемый код.

В обоих примерах слой p/invoke должен создать промежуточный объект с целью маршалинга. В первом примере этот объект не удаляется к тому моменту, когда вы снова вызовете неуправляемый код. Конечно, во втором примере это не так, поскольку вся работа происходит сразу.

Я считаю, что ваш второй пример должен быть безопасным в использовании. То есть, слой p/invoke достаточно умен, чтобы правильно обрабатывать ref. Первый пример ненадежен, потому что p/invoke используется неправильно, а не из-за какого-либо фундаментального ограничения параметров ref.


Несколько дополнительных пунктов:

  • я не использовал бы слово "небезопасный" здесь. Да, вызов неуправляемого кода в какой-то мере небезопасен, но в C# «небезопасно» имеет очень специфическое значение, связанное с использованием ключевого слова unsafe. Я ничего не вижу в вашем примере кода, который фактически использует unsafe.
  • В обоих примерах у вас есть ошибка, связанная с использованием делегата, переданного неуправляемому коду. В частности, в то время как слой p/invoke может перевести вашу управляемую ссылку делегата на указатель функции, который может использовать неуправляемый код, он ничего не знает об времени жизни объекта-делегата. Он сохранит объект достаточно долго, чтобы завершить вызов метода p/invoked, но если вам нужно, чтобы он работал дольше, чем это (как в данном случае), вам нужно сделать это самостоятельно. Например, используйте GC.KeepAlive() для переменной, в которой вы сохранили ссылку. (Вероятно, вы можете воспроизвести сбой, вставив вызов GC.Collect() между вызовом StoreCallback() и более поздним вызовом неуправляемого кода, в котором будет использоваться указатель функции).
+0

Вы правы о второй дополнительной точке. Вставка GC.Collect() вызвала сбой. Я бы предположил, что сохранение статической ссылки делегата также будет работать. –

Смежные вопросы