Поделиться через


Неуправляемые соглашения о вызовах

Соглашения о вызовах описывают низкоуровневые сведения о том, как аргументы метода и возвращаемые значения передаются между вызывающим и вызываемым методом.

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

Стандарт вызовов платформы по умолчанию

Большинство платформ используют одно каноническое соглашение о вызове, и явно указанное соглашение о вызове не требуется в большинстве случаев.

Для архитектуры x86 соглашение о вызовах по умолчанию зависит от платформы. Stdcall ("стандартный вызов") — это соглашение о вызовах по умолчанию для Windows x86 и используется большинством API Win32. Cdecl — это соглашение о вызовах по умолчанию в Linux x86. Порты Windows библиотек с открытым исходным кодом, пришедших из Unix, часто используют соглашение о вызове Cdecl даже в среде Windows x86. Необходимо явно указать Cdecl соглашение о вызовах в объявлениях P/Invoke для взаимодействия с этими библиотеками.

Для архитектур, отличных от x86, как соглашение о вызовах Stdcall, так и Cdecl рассматриваются как каноническое соглашение о вызове платформы по умолчанию.

Когда можно опустить конвенцию вызова

В архитектурах x64, ARM и ARM64 существует только одно соглашение о вызовах, поэтому явное указание не требуется. При выборе версии Windows x86 (32-разрядная версия) необходимо указать соглашение о вызовах только в том случае, если Stdcall и Cdecl отличаются.

  • ✔️ Указывайте соглашение о вызовах явным образом при выборе Windows x86.
  • ✔️ Не используйте соглашение о вызовах для x64, ARM и ARM64— атрибут не влияет на эти архитектуры.

Указание соглашений вызова в объявлениях управляемого P/Invoke

Соглашения о вызовах задаются типами в System.Runtime.CompilerServices пространстве имен или их сочетаниями:

Примеры явно указанных соглашений о вызовах:

using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

// P/Invoke declaration using SuppressGCTransition calling convention.
[LibraryImport("kernel32.dll")]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSuppressGCTransition) })]
extern static ulong GetTickCount64();

// Unmanaged callback with Cdecl calling convention.
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvCdecl) })]
static unsafe int NativeCallback(void* context);

// Method returning function pointer with combination of Cdecl and MemberFunction calling conventions.
static unsafe delegate* unmanaged[Cdecl, MemberFunction]<int> GetHandler();

Указание конвенций вызова в предыдущих версиях .NET

.NET Framework и версии .NET до .NET 5 ограничены подмножеством соглашений о вызовах, которые могут быть описаны перечислением CallingConvention.

Примеры явно указанных соглашений о вызовах:

using System.Runtime.InteropServices;

// P/Invoke declaration using Cdecl calling convention
[DllImport("ucrtbase.dll", CallingConvention=CallingConvention.Cdecl)]
static void* malloc(UIntPtr size);

// Delegate marshalled as callback with Cdecl calling convention
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void Callback(IntPtr context);

См. также