Маршалинг данных между управляемым и неуправляемым кодом - Атрибуты [InAttribute] и [OutAttribute]

ОГЛАВЛЕНИЕ

Атрибуты [InAttribute] и [OutAttribute]

Для начала рассмотрим порядок использования атрибутов InAttribute и OutAttribute — это типы атрибутов, существующие в пространстве имен System.Runtime.InteropServices. (В коде на языке C# или Visual Basic® эти атрибуты можно писать в сокращенной форме — [In] и [Out] — однако в статье мы будем придерживаться полных форм, дабы избежать путаницы.)

Если указанные атрибуты относятся к параметрам метода или возвращаемым значениям, они определяют направление маршалинга, поэтому их называют атрибутами направления. При наличии атрибута [InAttribute] среда CLR выполняет маршалинг данных от вызывающей стороны к вызываемой в начале вызова, а при наличии атрибута [OutAttribute] — обратный маршалинг от вызываемой стороны к вызывающей при возврате. И вызывающая, и вызываемая сторона может быть представлена как управляемым, так и неуправляемым кодом. Например, при вызове P/Invoke управляемый код осуществляет вызов неуправляемого кода, а при обратном вызове P/Invoke неуправляемый код может осуществлять вызов управляемого кода через указатель функции.

При использовании атрибутов [InAttribute] и [OutAttribute] возможны четыре комбинации: только [InAttribute], только [OutAttribute], оба атрибута [InAttribute, OutAttribute] или ни одного. Если ни один из атрибутов не указан, среда CLR самостоятельно определяет их значения. Обычно по умолчанию используется атрибут [InAttribute], но бывают и исключения. Например, к классу StringBuilder применяются оба атрибута, если они не указаны явно. (Далее мы рассмотрим класс StringBuilder более подробно.) Ключевые слова out и ref в языке C# также могут менять порядок применения атрибутов (см. рис. 2). Обратите внимание на то, что если для параметра не указано ключевого слова, то по умолчанию он считается входным параметром.

Figure 2 Out and Ref and Their Associated Attributes

Ключевое слово C# Атрибут
(нет) [InAttribute]
out [OutAttribute]
ref [InAttribute], [OutAttribute]

Рассмотрим код, приведенный на рис. 3. В нем использованы три функции на C++ в машинном коде, одинаковым образом изменяющие элемент arg. Отметим, что при работе со строками strcpy используется только в иллюстративных целях — в рабочем коде должны быть безопасные версии этих функций, о которых рассказывалось в статье msdn.microsoft.com/msdnmag/issues/05/05/SafeCandC.

Figure 3 Trying Out Directional Attributes
MARSHALLIB_API void __stdcall Func_In_Attribute(char *arg) 
{
printf("Inside Func_In_Attribute: arg = %s\n", arg);
strcpy(arg, "New");
}
MARSHALLIB_API void __stdcall Func_Out_Attribute(char *arg)
{
printf("Inside Func_Out_Attribute: arg = %s\n", arg);
strcpy(arg, "New");
}
MARSHALLIB_API void __stdcall Func_InOut_Attribute(char *arg)
{
printf("Inside Func_InOut_Attribute: arg = %s\n", arg);
strcpy(arg, "New");
}

Единственное различие состоит в том, как эти функции вызываются, то есть в применении атрибутов направления в сигнатурах P/Invoke:

[DllImport(@"MarshalLib.dll")]
public static extern void Func_In_Attribute([In]char[] arg);
[DllImport(@"MarshalLib.dll")]
public static extern void Func_Out_Attribute([Out]char[] arg);
[DllImport(@"MarshalLib.dll")]
public static extern void Func_InOut_Attribute([In, Out]char[] arg);

Если вызвать перечисленные функции из управляемого кода методом P/Invoke и передать им Old в качестве массива символов, мы получим на выходе следующее (в примере приведен фрагмент выходных данных):

Before Func_In_Attribute: arg = Old
Inside Func_In_Attribute: arg = Old
After Func_In_Attribute: arg = Old

Before Func_Out_Attribute: arg = Old
Inside Func_Out_Attribute: arg =
After Func_Out_Attribute: arg = New

Before Func_InOut_Attribute: arg = Old
Inside Func_InOut_Attribute: arg = Old
After Func_InOut_Attribute: arg = New

Остановимся чуть подробнее на полученных результатах. Функции Func_In_Attribute передается исходное значение, но изменение, вносимое в ходе работы функции, обратно не передается. Функции Func_Out_Attribute исходное значение не передается, а измененное передается обратно. Функции Func_InOut_Attribute передается исходное значение, а значение, измененное в ходе ее работы, передается обратно. Однако даже незначительное изменение функций может полностью изменить результаты. Для начала изменим собственные функции так, чтобы использовался юникод:

MARSHALLIB_API void __stdcall Func_Out_Attribute_Unicode(wchar_t *arg)
{
wprintf(L"Inside Func_Out_Attribute_Unicode: arg = %s\n", arg);
printf("Inside Func_Out_Attribute_Unicode: strcpy(arg, \"New\")\n");
wcscpy(arg, L"New");
}

Мы объявляем функцию C#, применяем только атрибут [OutAttribute] и устанавливаем для CharSet значение CharSet.Unicode.

[DllImport(@"MarshalLib.dll", CharSet=CharSet.Unicode)]
public static extern void Func_Out_Attribute_Unicode([Out]char[] arg);

Результат выглядит так:

Before Func_Out_Attribute_Unicode: arg = Old
Inside Func_Out_Attribute_Unicode: arg = Old
After Func_Out_Attribute_Unicode: arg = New

Обратите внимание: исходное значение передается, несмотря на то, что атрибут [InAttribute] указан не был. Атрибут [DllImportAttribute] заставляет среду CLR выполнять маршалинг юникода. Поскольку для символов в CLR также используется юникод, CLR считает возможным оптимизировать процесс маршалинга путем фиксации массива символов и передачи непосредственно адресов символов. (Копирование и фиксация будут рассмотрены дальше.) На такое поведение среды полагаться не стоит. При маршалинге нужно всегда указывать атрибуты направления, если не используется стандартное поведение среды CLR. Наглядным примером стандартного поведения служит аргумент int: указывать [InAttribute] int arg не обязательно.

Существуют ситуации, в которых атрибут [OutAttribute] игнорируется. Например, выражение [OutAttribute]int не имеет смысла, поэтому среда CLR попросту пропускает атрибут [OutAttribute]. То же самое верно и для выражения [OutAttribute] string, поскольку объект string является неизменяемым.

В файлах определений интерфейса (IDL) существуют атрибуты [in] и [out] — их можно считать аналогами атрибутов [InAttribute] и [OutAttribute] в среде CLR.