2023/12/02(土)追記: Breaking change: SafeHandle types must have public constructor - .NET | Microsoft Learnで本破壊的変更が明記されていました!
本記事のタイトルをより厳密に記述すると、「LibraryImportAttribute
を使ったメソッドの戻り値やout
引数、ref
引数に自作のSafeHandle
派生型を使っている場合は、.NET 8からはその型の引数なしコンストラクタのアクセス修飾子をpublic
にする必要がある」です。以降、実験に使用したコードや、ドキュメントを調べた結果を記述します。
まとめ
LibraryImportAttribute
で使用する自作SafeHandle型の引数なしコンストラクタは、.NET 7ではinternal
等で問題ありませんが、.NET 8ではpublic
必須な場合があります。LibraryImportAttribute
の互換性のドキュメントを見るに、.NET 8での変更は意図的であるようです。 → Breaking change: SafeHandle types must have public constructor - .NET | Microsoft Learn記事が存在するため、確実に意図的な変更です。- .NET 8で引数なしコンストラクタを
public
にしていなかった場合に出るSYSLIB1051
エラーが、メッセージからは全く原因が分からないのが辛いです……。
使用バージョン
.NETバージョンはSystem.Runtime.InteropServices.RuntimeInformation.FrameworkDescription
で確認しました。
- Microsoft Visual Studio Community 2022 (64-bit) - Current Version 17.8.0
- .NET 7.0.14
- .NET 8.0.0
動作検証用コード
自作SafeHandle
型として、SafeThreadHandle
型を定義します。その型の引数なしコンストラクタのアクセス修飾子がpublic
かどうか、というのが本記事の主題です。型定義は後述します。なお、その型にした理由は、Microsoft.Win32.SafeHandles
名前空間にSafeProcessHandle
型はありますが、SafeThreadHandle
は無いためです。
次に、LibraryImportAttribute
を使用してSource Generatorを使用する、P/Invoke用のNativeMethods
型を定義します:
using System; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; internal static partial class NativeMethods { // 自作SafeHandle型を戻り値に使う例 [LibraryImport("kernel32.dll", SetLastError = true)] internal static partial SafeThreadHandle OpenThread( uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId); // 自作SafeHandle型をout引数に使う例 [LibraryImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool DuplicateHandle( SafeProcessHandle hSourceProcessHandle, SafeThreadHandle hSourceHandle, SafeProcessHandle hTargetProcessHandle, out SafeThreadHandle lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions); [LibraryImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool CloseHandle(IntPtr hObject); }
NativeMethods
型の定義は、以降すべてで共通です。
.NET 7では、LibraryImportAttribute
で使用する自作SafeHandle
型の引数なしコンストラクタのアクセス修飾子がinternal
でも問題なかった
本節では、<プロジェクト名>.csproj
に以下の内容を使用します:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net7.0</TargetFramework> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> </Project>
自作SafeHandle型として、以下のコードを使用します:
using System; using System.Runtime.InteropServices; internal sealed class SafeThreadHandle : SafeHandle { internal SafeThreadHandle() : base(IntPtr.Zero, true) { } public override bool IsInvalid => this.handle == IntPtr.Zero; protected override bool ReleaseHandle() => NativeMethods.CloseHandle(this.handle); }
引数なしコンストラクタのアクセス修飾子がinternal
であることに注目してください。その場合でも、.NET 7ではLibraryImportAttribute
によるソース生成に成功します。生成結果です:
// <auto-generated/> internal static unsafe partial class NativeMethods { [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "7.0.9.1910")] [System.Runtime.CompilerServices.SkipLocalsInitAttribute] internal static partial global::SafeThreadHandle OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId) { int __lastError; bool __invokeSucceeded = default; int __bInheritHandle_native = default; global::SafeThreadHandle __retVal; System.IntPtr __retVal_native = default; // Setup - Perform required setup. __retVal = new global::SafeThreadHandle(); try { // Marshal - Convert managed data to native data. __bInheritHandle_native = (int)(bInheritHandle ? 1 : 0); { System.Runtime.InteropServices.Marshal.SetLastSystemError(0); __retVal_native = __PInvoke(dwDesiredAccess, __bInheritHandle_native, dwThreadId); __lastError = System.Runtime.InteropServices.Marshal.GetLastSystemError(); } __invokeSucceeded = true; } finally { if (__invokeSucceeded) { // GuaranteedUnmarshal - Convert native data to managed data even in the case of an exception during the non-cleanup phases. System.Runtime.InteropServices.Marshal.InitHandle(__retVal, __retVal_native); } } System.Runtime.InteropServices.Marshal.SetLastPInvokeError(__lastError); return __retVal; // Local P/Invoke [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "OpenThread", ExactSpelling = true)] static extern unsafe System.IntPtr __PInvoke(uint dwDesiredAccess, int bInheritHandle, uint dwThreadId); } } internal static unsafe partial class NativeMethods { [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "7.0.9.1910")] [System.Runtime.CompilerServices.SkipLocalsInitAttribute] internal static partial bool DuplicateHandle(global::Microsoft.Win32.SafeHandles.SafeProcessHandle hSourceProcessHandle, global::SafeThreadHandle hSourceHandle, global::Microsoft.Win32.SafeHandles.SafeProcessHandle hTargetProcessHandle, out global::SafeThreadHandle lpTargetHandle, uint dwDesiredAccess, bool bInheritHandle, uint dwOptions) { int __lastError; bool __invokeSucceeded = default; System.Runtime.CompilerServices.Unsafe.SkipInit(out lpTargetHandle); System.IntPtr __hSourceProcessHandle_native = default; System.IntPtr __hSourceHandle_native = default; System.IntPtr __hTargetProcessHandle_native = default; System.IntPtr __lpTargetHandle_native = default; int __bInheritHandle_native = default; bool __retVal; int __retVal_native = default; // Setup - Perform required setup. bool hSourceProcessHandle__addRefd = false; bool hSourceHandle__addRefd = false; bool hTargetProcessHandle__addRefd = false; global::SafeThreadHandle lpTargetHandle__newHandle = new global::SafeThreadHandle(); try { // Marshal - Convert managed data to native data. hSourceProcessHandle.DangerousAddRef(ref hSourceProcessHandle__addRefd); __hSourceProcessHandle_native = hSourceProcessHandle.DangerousGetHandle(); hSourceHandle.DangerousAddRef(ref hSourceHandle__addRefd); __hSourceHandle_native = hSourceHandle.DangerousGetHandle(); hTargetProcessHandle.DangerousAddRef(ref hTargetProcessHandle__addRefd); __hTargetProcessHandle_native = hTargetProcessHandle.DangerousGetHandle(); __bInheritHandle_native = (int)(bInheritHandle ? 1 : 0); { System.Runtime.InteropServices.Marshal.SetLastSystemError(0); __retVal_native = __PInvoke(__hSourceProcessHandle_native, __hSourceHandle_native, __hTargetProcessHandle_native, &__lpTargetHandle_native, dwDesiredAccess, __bInheritHandle_native, dwOptions); __lastError = System.Runtime.InteropServices.Marshal.GetLastSystemError(); } __invokeSucceeded = true; // Unmarshal - Convert native data to managed data. __retVal = __retVal_native != 0; } finally { if (__invokeSucceeded) { // GuaranteedUnmarshal - Convert native data to managed data even in the case of an exception during the non-cleanup phases. System.Runtime.InteropServices.Marshal.InitHandle(lpTargetHandle__newHandle, __lpTargetHandle_native); lpTargetHandle = lpTargetHandle__newHandle; } // Cleanup - Perform required cleanup. if (hSourceProcessHandle__addRefd) hSourceProcessHandle.DangerousRelease(); if (hSourceHandle__addRefd) hSourceHandle.DangerousRelease(); if (hTargetProcessHandle__addRefd) hTargetProcessHandle.DangerousRelease(); } System.Runtime.InteropServices.Marshal.SetLastPInvokeError(__lastError); return __retVal; // Local P/Invoke [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "DuplicateHandle", ExactSpelling = true)] static extern unsafe int __PInvoke(System.IntPtr hSourceProcessHandle, System.IntPtr hSourceHandle, System.IntPtr hTargetProcessHandle, System.IntPtr* lpTargetHandle, uint dwDesiredAccess, int bInheritHandle, uint dwOptions); } } // 今回の記事に無関係であるためCloseHandleは省略
自作SafeHandle
型を戻り値とするOpenThread
メソッドの場合、以下のようにnew
演算子でインスタンスを構築しています:
global::SafeThreadHandle __retVal; // 略 __retVal = new global::SafeThreadHandle();
同様に、自作SafeHandle
型をout
引数にするDuplicateHandle
メソッドの場合も、以下のようにnew
演算子でインスタンスを構築しています:
global::SafeThreadHandle lpTargetHandle__newHandle = new global::SafeThreadHandle();
どちらの場合でも、自動生成されたソースはnew
演算子で引数なしコンストラクタを呼び出しています。自動生成されたソースは同一アセンブリにコンパイルされることから、自作SafeHandle
型の引数なしコンストラクタのアクセス修飾子がinternal
である場合でも、問題なく呼び出せます。
.NET 8では、LibraryImportAttribute
で使用する自作SafeHandle
型の引数なしコンストラクタのアクセス修飾子がinternal
だとSYSLIB1051
エラーになる
本節では、<プロジェクト名>.csproj
に以下の内容を使用します:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> </Project>
自作SafeHandle型として、以下のコードを使用します:
using System; using System.Runtime.InteropServices; internal sealed class SafeThreadHandle : SafeHandle { internal SafeThreadHandle() : base(IntPtr.Zero, true) { } public override bool IsInvalid => this.handle == IntPtr.Zero; protected override bool ReleaseHandle() => NativeMethods.CloseHandle(this.handle); }
引き続き、引数なしコンストラクタのアクセス修飾子はinternal
です。この場合、.NET 8では以下のエラーが発生します:
Error SYSLIB1051 The specified parameter needs to be marshalled from unmanaged to managed, but the marshaller type 'global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::SafeThreadHandle>' does not support it. The generated source will not handle marshalling of the return value of method 'OpenThread'. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1051)
Error SYSLIB1051 The specified parameter needs to be marshalled from unmanaged to managed, but the marshaller type 'global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::SafeThreadHandle>' does not support it. The generated source will not handle marshalling of parameter 'lpTargetHandle'. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1051)
エラーメッセージから、the marshaller type 'global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::SafeThreadHandle>' does not support it
らしいこと、つまりSafeHandleMarshaller<global::SafeThreadHandle>
型を使おうとしますが未サポートであるため失敗したことが分かります。しかし、引数なしコンストラクタのアクセス修飾子が問題であることは全くわかりません。辛いです。
.NET 8では、LibraryImportAttribute
で使用する自作SafeHandle
型の引数なしコンストラクタのアクセス修飾子はpublic
にする必要がある
本節では、<プロジェクト名>.csproj
に以下の内容を引き続き使用します:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> </Project>
自作SafeHandle型として、以下のコードを使用します:
using System; using System.Runtime.InteropServices; internal sealed class SafeThreadHandle : SafeHandle { public SafeThreadHandle() : base(IntPtr.Zero, true) { } public override bool IsInvalid => this.handle == IntPtr.Zero; protected override bool ReleaseHandle() => NativeMethods.CloseHandle(this.handle); }
引数なしコンストラクタのアクセス修飾子をpublic
へ変更しています。こうすると、.NET 8でもLibraryImportAttribute
によるソース生成に成功します。生成結果です:
// <auto-generated/> internal static unsafe partial class NativeMethods { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "8.0.9.3103")] [global::System.Runtime.CompilerServices.SkipLocalsInitAttribute] internal static partial global::SafeThreadHandle OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId) { int __lastError; bool __invokeSucceeded = default; int __bInheritHandle_native = default; global::SafeThreadHandle __retVal = default; nint __retVal_native = default; // Setup - Perform required setup. global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::SafeThreadHandle>.ManagedToUnmanagedOut __retVal_native__marshaller = new(); try { // Marshal - Convert managed data to native data. __bInheritHandle_native = (int)(bInheritHandle ? 1 : 0); { global::System.Runtime.InteropServices.Marshal.SetLastSystemError(0); __retVal_native = __PInvoke(dwDesiredAccess, __bInheritHandle_native, dwThreadId); __lastError = global::System.Runtime.InteropServices.Marshal.GetLastSystemError(); } __invokeSucceeded = true; // UnmarshalCapture - Capture the native data into marshaller instances in case conversion to managed data throws an exception. __retVal_native__marshaller.FromUnmanaged(__retVal_native); // Unmarshal - Convert native data to managed data. __retVal = __retVal_native__marshaller.ToManaged(); } finally { if (__invokeSucceeded) { // CleanupCalleeAllocated - Perform cleanup of callee allocated resources. __retVal_native__marshaller.Free(); } } global::System.Runtime.InteropServices.Marshal.SetLastPInvokeError(__lastError); return __retVal; // Local P/Invoke [global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "OpenThread", ExactSpelling = true)] static extern unsafe nint __PInvoke(uint __dwDesiredAccess_native, int __bInheritHandle_native, uint __dwThreadId_native); } } internal static unsafe partial class NativeMethods { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "8.0.9.3103")] [global::System.Runtime.CompilerServices.SkipLocalsInitAttribute] internal static partial bool DuplicateHandle(global::Microsoft.Win32.SafeHandles.SafeProcessHandle hSourceProcessHandle, global::SafeThreadHandle hSourceHandle, global::Microsoft.Win32.SafeHandles.SafeProcessHandle hTargetProcessHandle, out global::SafeThreadHandle lpTargetHandle, uint dwDesiredAccess, bool bInheritHandle, uint dwOptions) { int __lastError; bool __invokeSucceeded = default; global::System.Runtime.CompilerServices.Unsafe.SkipInit(out lpTargetHandle); nint __hSourceProcessHandle_native = default; nint __hSourceHandle_native = default; nint __hTargetProcessHandle_native = default; nint __lpTargetHandle_native = default; int __bInheritHandle_native = default; bool __retVal = default; int __retVal_native = default; // Setup - Perform required setup. global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::SafeThreadHandle>.ManagedToUnmanagedOut __lpTargetHandle_native__marshaller = new(); global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::Microsoft.Win32.SafeHandles.SafeProcessHandle>.ManagedToUnmanagedIn __hTargetProcessHandle_native__marshaller = new(); global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::SafeThreadHandle>.ManagedToUnmanagedIn __hSourceHandle_native__marshaller = new(); global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::Microsoft.Win32.SafeHandles.SafeProcessHandle>.ManagedToUnmanagedIn __hSourceProcessHandle_native__marshaller = new(); try { // Marshal - Convert managed data to native data. __bInheritHandle_native = (int)(bInheritHandle ? 1 : 0); __hTargetProcessHandle_native__marshaller.FromManaged(hTargetProcessHandle); __hSourceHandle_native__marshaller.FromManaged(hSourceHandle); __hSourceProcessHandle_native__marshaller.FromManaged(hSourceProcessHandle); { // PinnedMarshal - Convert managed data to native data that requires the managed data to be pinned. __hTargetProcessHandle_native = __hTargetProcessHandle_native__marshaller.ToUnmanaged(); __hSourceHandle_native = __hSourceHandle_native__marshaller.ToUnmanaged(); __hSourceProcessHandle_native = __hSourceProcessHandle_native__marshaller.ToUnmanaged(); global::System.Runtime.InteropServices.Marshal.SetLastSystemError(0); __retVal_native = __PInvoke(__hSourceProcessHandle_native, __hSourceHandle_native, __hTargetProcessHandle_native, &__lpTargetHandle_native, dwDesiredAccess, __bInheritHandle_native, dwOptions); __lastError = global::System.Runtime.InteropServices.Marshal.GetLastSystemError(); } __invokeSucceeded = true; // UnmarshalCapture - Capture the native data into marshaller instances in case conversion to managed data throws an exception. __lpTargetHandle_native__marshaller.FromUnmanaged(__lpTargetHandle_native); // Unmarshal - Convert native data to managed data. __retVal = __retVal_native != 0; lpTargetHandle = __lpTargetHandle_native__marshaller.ToManaged(); } finally { if (__invokeSucceeded) { // CleanupCalleeAllocated - Perform cleanup of callee allocated resources. __lpTargetHandle_native__marshaller.Free(); } // CleanupCallerAllocated - Perform cleanup of caller allocated resources. __hTargetProcessHandle_native__marshaller.Free(); __hSourceHandle_native__marshaller.Free(); __hSourceProcessHandle_native__marshaller.Free(); } global::System.Runtime.InteropServices.Marshal.SetLastPInvokeError(__lastError); return __retVal; // Local P/Invoke [global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "DuplicateHandle", ExactSpelling = true)] static extern unsafe int __PInvoke(nint __hSourceProcessHandle_native, nint __hSourceHandle_native, nint __hTargetProcessHandle_native, nint* __lpTargetHandle_native, uint __dwDesiredAccess_native, int __bInheritHandle_native, uint __dwOptions_native); } } // 今回の記事に無関係であるためCloseHandleは省略
自作SafeHandle
型を戻り値とするOpenThread
メソッドの場合、以下のようにSystem.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<T>.ManagedToUnmanagedOut
型経由でインスタンスを構築しています:
global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::SafeThreadHandle>.ManagedToUnmanagedOut __retVal_native__marshaller = new(); // 略 __retVal_native__marshaller.FromUnmanaged(__retVal_native); // Unmarshal - Convert native data to managed data. __retVal = __retVal_native__marshaller.ToManaged();
同様に、自作SafeHandle
型をout
引数にするDuplicateHandle
メソッドの場合も、以下のようにSystem.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<T>.ManagedToUnmanagedOut
型経由でインスタンスを構築しています:
global::System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<global::SafeThreadHandle>.ManagedToUnmanagedOut __lpTargetHandle_native__marshaller = new(); // 略 __lpTargetHandle_native__marshaller.FromUnmanaged(__lpTargetHandle_native); // 略 lpTargetHandle = __lpTargetHandle_native__marshaller.ToManaged();
どちらの場合でも、自動生成されたソースはSystem.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<T>.ManagedToUnmanagedOut Structを経由して、自作SafeHandle
型インスタンスを構築しています。
本節で記述したように、メソッドの戻り値やout
引数で自作SafeHandle
型を使用する場合には、引数なしコンストラクタのアクセス修飾子はpublic
である必要があります。一方でそれ以外の場合、つまり値渡し用途の引数で自作SafeHandle
型を使用する場合は、引数なしコンストラクタを使わないためアクセス修飾子は自由であるはずです。
なぜ.NET 8では自作SafeHandle
型の引数なしコンストラクタに要求されるアクセス修飾子が変化したのか
以降はドキュメント類を探し回った結果を記述します。
SafeHandleMarshaller<T>
型とは
System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<T> Classは.NET 8で追加された型です。しかしドキュメントを読んでも役立つ説明はほとんどありません。
SafeHandleMarshaller<T>
型の追加を提案するissueを探すと[API Proposal]: Marshaller type to support SafeHandle in source-generated marshalling without hard-coding in the generator · Issue #74035にありました。提案issueによると、以下の理由で追加したいとのことです:
- (当該issue作成時点で)
SafeHandle
型は、現在Source Generatorが特別にマーシャリングしている唯一の型です。string
型はかつてはSource Generatorが特別にマーシャリングしていましたが、.NET 7開発時点ですでに特別扱いでは無くなっています(筆者注: Utf16StringMarshallerなどを経由するようになった、という意味だと思います)。
- Source Generatorが行っている
SafeHandle
型用の特別なマーシャリングは、他の組み込み型のマーシャリングと比較して、非常に複雑です。 - 上記の理由のため、Source Generatorが
SafeHandle
型をマーシャリングするためのコードを直接出力する代わりのための、マーシャリングを行う型を導入したいです。それにより、相互運用コードのサービス性(筆者注:よく分からず)が向上します。
なお提案Issueには、「ref
引数とout
引数のために(SafeHandleMarshaller<T>
型の)型引数にnew()
制約を追加することも考えましたが、in
引数や値渡し引数を使用しているAPIに破壊的変更を加えることになるため、取り下げました」という旨のAlternative Designs
セクションの記述があります。
ソースレベルでは、引数なしコンストラクタのアクセス修飾子がpublicでなくても、ソース生成そのものはさせる方針だったらしい痕跡がある
System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<T>.ManagedToUnmanagedOut
のソースを探すと、以下の記述があります:
public ManagedToUnmanagedOut() { _initialized = false; // SafeHandle out marshalling has always required parameterless constructors, // but it has never required them to be public. // We construct the handle now to ensure we don't cause an exception // before we are able to capture the unmanaged handle after the call. _newHandle = Activator.CreateInstance<T>()!; }
コメントに、「SafeHandle
型のout
引数用マーシャリングでは、引数なしコンストラクタは要求してきた。しかしそれがpublic
であることは要求してこなかった。そのためこの時点でインスタンス構築を試みて、成功するかを検証する」ということが書かれています。つまり、当該コードが記述された時点では、引数なしコンストラクタのアクセス修飾子にかかわらず、System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<T>.ManagedToUnmanagedOut
を経由してインスタンスを生成するコードを自動生成させる設計だったようです。
しかし後に設計が変わった模様
LibraryImportAttribute
の互換性のドキュメントを見ると、以下の記述があります:
Safe Handles Due to trimming issues with NativeAOT's implementation of Activator.CreateInstance, we have decided to change our recommendation of providing a public parameterless constructor for ref, out, and return scenarios to a requirement. We already required a parameterless constructor of some visibility, so changing to a requirement matches our design principles of taking breaking changes to make interop more understandable and enforce more of our best practices instead of going out of our way to provide backward compatibility at increasing costs.
つまり、Activator.CreateInstance
ではAOTコンパイル時に問題が起こったため、SafeHandle
型をref
引数やout
引数、戻り値として使用する場合について、引数なしコンストラクタのアクセス修飾子をpublic
にすることを、推奨要件から必須要件へ変更したとのことです。そのような理由で.NET 8リリースでは、引数なしコンストラクタのアクセス修飾子がpublic
ではない場合に、SYSLIB1051
エラーが発生するようになったようです。
そもそもちゃんと、破壊的変更と明記されていました
2023/12/02(土)に、Breaking change: SafeHandle types must have public constructor - .NET | Microsoft Learn記事を見つけました。意図された破壊的変更です!
感想
私の手元のプロジェクトを.NET 7から.NET 8へ移行しようとしたときに、今回の問題へ遭遇しました。SYSLIB1051
エラーが発生する理由が全く分からず、しばらく悩みました。SYSLIB1051
エラーが発生しているメソッドの傾向から推測して、どうにか原因を突き止められました。大変でした。
本記事の内容にハマる人がどれほどいるかは分かりませんが、ハマった人の参考になれば幸いです。