В чем разница между [In, Out] и ref при использовании pinvoke в C#?

Есть ли разница между использованием [In, Out] и просто использованием ref при передаче параметров из C# в C++?

Я нашел пару разных SO сообщений, а также кое-что из MSDN, которое близко подходит к моему вопросу, но не совсем отвечает на него. Я предполагаю, что я могу безопасно использовать ref точно так же, как я бы использовал [In, Out], и что маршаллер не будет действовать иначе. Меня беспокоит то, что он будет действовать по-другому, и что C++ не будет доволен передачей моей структуры C#. Я видел обе вещи в кодовой базе, в которой я работаю...

Вот сообщения, которые я нашел и прочитал:

Являются ли атрибуты P/Invoke [In, Out] необязательными для маршалинга массивов? Заставляет меня думать, что я должен использовать [In, Out].

Эти три сообщения заставляют меня думать, что я должен использовать [In, Out], но вместо этого я могу использовать ref, и у него будет тот же машинный код. Это заставляет меня думать, что я не прав - поэтому спрашиваю здесь.

1 ответ

Решение

Использование ref или же out не произвольно. Если нативный код требует передачи по ссылке (указатель), вы должны использовать эти ключевые слова, если тип параметра является типом значения. Так что джиттер знает, чтобы сгенерировать указатель на значение. И вы должны их опустить, если тип параметра является ссылочным типом (классом), объекты уже являются указателями.

Атрибуты [In] и [Out] необходимы для устранения неоднозначности в отношении указателей, они не определяют поток данных. [In] всегда подразумевается маршаллером пинвока, поэтому указывать явно не нужно. Но вы должны использовать [Out], если ожидаете увидеть какие-либо изменения, сделанные нативным кодом, в структуре или члене класса обратно в ваш код. Маршаллер Pinvoke избегает автоматического копирования, чтобы избежать расходов.

Еще одна странность заключается в том, что [Out] не часто необходимо. Происходит, когда значение является blittable, дорогое слово, которое означает, что управляемое значение или макет объекта идентичны нативному макету. Затем маршаллер pinvoke может сделать ярлык, закрепить объект и передать указатель на хранилище управляемого объекта. Тогда вы неизбежно увидите изменения, поскольку нативный код напрямую изменяет управляемый объект.

Что-то, чего вы вообще сильно хотите добиться, это очень эффективно. Вы помогаете, предоставив типу атрибут [StructLayout(LayoutKind.Sequential)], он подавляет оптимизацию, которую использует CLR для переупорядочения полей, чтобы получить наименьший объект. И используя только поля простых типов значений или буферов фиксированного размера, хотя у вас не часто есть этот выбор. Никогда не используйте bool, используйте вместо него byte. Нет простого способа выяснить, является ли тип blittable, кроме того, что он работает некорректно или с помощью отладчика, и сравнить значения указателя.

Просто будьте явными и всегда используйте [Out], когда вам это нужно. Это ничего не стоит, если в этом нет необходимости. И это самодокументируется. И вы можете чувствовать себя хорошо, что он все равно будет работать, если архитектура нативного кода изменится.

Другие вопросы по тегам