.NET 5, .NET Framework에서만 허용하는 UnmanagedCallersOnly 사용예
예를 들어 함수 포인터를 사용하는 아래의 코드는,
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
class Program
{
[DllImport("user32.dll", ExactSpelling = true)]
unsafe static extern IntPtr SetTimer(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, delegate* unmanaged[Stdcall]<IntPtr, uint, IntPtr, uint, void> lpTimerFunc);
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvStdcall) })]
static void timerCallback(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime)
{
System.Diagnostics.Trace.WriteLine($"timerCallback - {dwTime}");
}
unsafe static void Main(string[] args)
{
SetTimer(IntPtr.Zero, IntPtr.Zero, 1000, &timerCallback);
}
}
#if !NET5_0
namespace System.Runtime.InteropServices
{
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class UnmanagedCallersOnlyAttribute : Attribute
{
public Type[] CallConvs;
public string EntryPoint;
}
}
#endif
.NET Framework + C# 9.0 또는 .NET 5 + C# 9.0 환경에서는 컴파일이 잘 됩니다. 그런데 동일한 소스 코드를 .NET Core 3.1 이하의 환경에서 하면 이런 컴파일 오류가 발생합니다.
// error CS8893: 'CallConvStdcall' is not a valid calling convention type for 'UnmanagedCallersOnly'.
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvStdcall) })]
// error CS8786: Calling convention of 'Program.timerCallback(IntPtr, uint, IntPtr, uint)' is not compatible with 'Standard'.
SetTimer(IntPtr.Zero, IntPtr.Zero, 1000, &timerCallback);
이것이 C# 9.0 컴파일러의 버그인지, 아니면 의도한 것인지는 정확하게 알 수 없습니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
(결국 불가능하지만) C# 구문 상으로 보면 .NET Core에서 이를 회피할 수 있는 방법이 있습니다. x64의 경우 사실 호출 규약이 하나로 통일되었으므로 굳이 이를 지정할 필요가 없어 다음과 같은 식으로 코딩하는 것도 가능합니다.
unsafe static extern IntPtr SetTimer(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, delegate* unmanaged<IntPtr, uint, IntPtr, uint, void> lpTimerFunc);
[UnmanagedCallersOnly]
unsafe static void timerCallback(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime)
{
System.Diagnostics.Trace.WriteLine($"timerCallback - {dwTime}");
}
재미있는 것은, 저렇게 호출 규약을 생략하는
"delegate* unmanaged" 구문은 유일하게 닷넷 5에서만 가능하다는 점입니다. 즉, 런타임 코드가 수정되었다는 건데, 닷넷 5는 호출 규약을 명시하지 않은 경우 프로그램의 실행 환경에 따라 기본 호출 규약을 정해주는 기능이 있기 때문입니다.
따라서, 어떤 식으로 해도 현재 .NET Core 3.1 이하의 환경에서는 저 코드를 컴파일할 수 없습니다.
(그러게요, 정말 의문이군요, 버그인지, 의도한 것인지!)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]