11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . Diagnostics ;
45using System . Diagnostics . CodeAnalysis ;
56using System . Runtime . InteropServices ;
67using Microsoft . Quic ;
@@ -47,7 +48,8 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)
4748 }
4849 }
4950
50- internal static MsQuicApi Api { get ; } = null ! ;
51+ private static readonly Lazy < MsQuicApi > _api = new Lazy < MsQuicApi > ( AllocateMsQuicApi ) ;
52+ internal static MsQuicApi Api => _api . Value ;
5153
5254 internal static bool IsQuicSupported { get ; }
5355
@@ -58,29 +60,21 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)
5860
5961 static MsQuicApi ( )
6062 {
61- IntPtr msQuicHandle ;
62- if ( ! NativeLibrary . TryLoad ( $ "{ Interop . Libraries . MsQuic } .{ MsQuicVersion . Major } ", typeof ( MsQuicApi ) . Assembly , DllImportSearchPath . AssemblyDirectory , out msQuicHandle ) &&
63- ! NativeLibrary . TryLoad ( Interop . Libraries . MsQuic , typeof ( MsQuicApi ) . Assembly , DllImportSearchPath . AssemblyDirectory , out msQuicHandle ) )
63+ if ( ! TryLoadMsQuic ( out IntPtr msQuicHandle ) )
6464 {
6565 return ;
6666 }
6767
6868 try
6969 {
70- if ( ! NativeLibrary . TryGetExport ( msQuicHandle , "MsQuicOpenVersion" , out IntPtr msQuicOpenVersionAddress ) )
71- {
72- return ;
73- }
74-
75- QUIC_API_TABLE * apiTable = null ;
76- delegate * unmanaged[ Cdecl] < uint , QUIC_API_TABLE * * , int > msQuicOpenVersion = ( delegate * unmanaged[ Cdecl] < uint , QUIC_API_TABLE * * , int > ) msQuicOpenVersionAddress ;
77- if ( StatusFailed ( msQuicOpenVersion ( ( uint ) MsQuicVersion . Major , & apiTable ) ) )
70+ if ( ! TryOpenMsQuic ( msQuicHandle , out QUIC_API_TABLE * apiTable ) )
7871 {
7972 return ;
8073 }
8174
8275 try
8376 {
77+ // Check version
8478 int arraySize = 4 ;
8579 uint * libVersion = stackalloc uint [ arraySize ] ;
8680 uint size = ( uint ) arraySize * sizeof ( uint ) ;
@@ -99,7 +93,7 @@ static MsQuicApi()
9993 return ;
10094 }
10195
102- // Assume SChannel is being used on windows and query for the actual provider from the library
96+ // Assume SChannel is being used on windows and query for the actual provider from the library if querying is supported
10397 QUIC_TLS_PROVIDER provider = OperatingSystem . IsWindows ( ) ? QUIC_TLS_PROVIDER . SCHANNEL : QUIC_TLS_PROVIDER . OPENSSL ;
10498 size = sizeof ( QUIC_TLS_PROVIDER ) ;
10599 apiTable ->GetParam ( null , QUIC_PARAM_GLOBAL_TLS_PROVIDER , & size , & provider ) ;
@@ -122,26 +116,67 @@ static MsQuicApi()
122116 Tls13ClientMayBeDisabled = IsTls13Disabled ( isServer : false ) ;
123117 }
124118
125- Api = new MsQuicApi ( apiTable ) ;
126119 IsQuicSupported = true ;
127120 }
128121 finally
129122 {
130- if ( ! IsQuicSupported && NativeLibrary . TryGetExport ( msQuicHandle , "MsQuicClose" , out IntPtr msQuicClose ) )
131- {
132- // Gracefully close the API table
133- ( ( delegate * unmanaged[ Cdecl] < QUIC_API_TABLE * , void > ) msQuicClose ) ( apiTable ) ;
134- }
123+ // Gracefully close the API table to free resources. The API table will be allocated lazily again if needed
124+ bool closed = TryCloseMsQuic ( msQuicHandle , apiTable ) ;
125+ Debug . Assert ( closed , "Failed to close MsQuic" ) ;
135126 }
136-
137127 }
138128 finally
139129 {
140- if ( ! IsQuicSupported )
141- {
142- NativeLibrary . Free ( msQuicHandle ) ;
143- }
130+ // Unload the library, we will load it again when we actually use QUIC
131+ NativeLibrary . Free ( msQuicHandle ) ;
132+ }
133+ }
134+
135+ private static MsQuicApi AllocateMsQuicApi ( )
136+ {
137+ Debug . Assert ( IsQuicSupported ) ;
138+
139+ if ( TryLoadMsQuic ( out IntPtr msQuicHandle ) &&
140+ TryOpenMsQuic ( msQuicHandle , out QUIC_API_TABLE * apiTable ) )
141+ {
142+ return new MsQuicApi ( apiTable ) ;
143+ }
144+
145+ throw new Exception ( "Failed to create MsQuicApi instance" ) ;
146+ }
147+
148+ private static bool TryLoadMsQuic ( out IntPtr msQuicHandle ) =>
149+ NativeLibrary . TryLoad ( $ "{ Interop . Libraries . MsQuic } .{ MsQuicVersion . Major } ", typeof ( MsQuicApi ) . Assembly , DllImportSearchPath . AssemblyDirectory , out msQuicHandle ) ||
150+ NativeLibrary . TryLoad ( Interop . Libraries . MsQuic , typeof ( MsQuicApi ) . Assembly , DllImportSearchPath . AssemblyDirectory , out msQuicHandle ) ;
151+
152+ private static bool TryOpenMsQuic ( IntPtr msQuicHandle , out QUIC_API_TABLE * apiTable )
153+ {
154+ apiTable = null ;
155+ if ( ! NativeLibrary . TryGetExport ( msQuicHandle , "MsQuicOpenVersion" , out IntPtr msQuicOpenVersionAddress ) )
156+ {
157+ return false ;
158+ }
159+
160+ QUIC_API_TABLE * table = null ;
161+ delegate * unmanaged[ Cdecl] < uint , QUIC_API_TABLE * * , int > msQuicOpenVersion = ( delegate * unmanaged[ Cdecl] < uint , QUIC_API_TABLE * * , int > ) msQuicOpenVersionAddress ;
162+ if ( StatusFailed ( msQuicOpenVersion ( ( uint ) MsQuicVersion . Major , & table ) ) )
163+ {
164+ return false ;
144165 }
166+
167+ apiTable = table ;
168+ return true ;
169+ }
170+
171+ private static bool TryCloseMsQuic ( IntPtr msQuicHandle , QUIC_API_TABLE * apiTable )
172+ {
173+ if ( NativeLibrary . TryGetExport ( msQuicHandle , "MsQuicClose" , out IntPtr msQuicClose ) )
174+ {
175+ ( ( delegate * unmanaged[ Cdecl] < QUIC_API_TABLE * , void > ) msQuicClose ) ( apiTable ) ;
176+ return true ;
177+ }
178+
179+ return false ;
145180 }
146181
147182 private static bool IsWindowsVersionSupported ( ) => OperatingSystem . IsWindowsVersionAtLeast ( MinWindowsVersion . Major ,
0 commit comments