@@ -48,6 +48,8 @@ public partial class SqliteConnection : DbConnection
4848 private static readonly StateChangeEventArgs _fromClosedToOpenEventArgs = new StateChangeEventArgs ( ConnectionState . Closed , ConnectionState . Open ) ;
4949 private static readonly StateChangeEventArgs _fromOpenToClosedEventArgs = new StateChangeEventArgs ( ConnectionState . Open , ConnectionState . Closed ) ;
5050
51+ private static string [ ] ? NativeDllSearchDirectories ;
52+
5153 static SqliteConnection ( )
5254 {
5355 Type . GetType ( "SQLitePCL.Batteries_V2, SQLitePCLRaw.batteries_v2" )
@@ -624,11 +626,71 @@ public virtual void LoadExtension(string file, string? proc = null)
624626
625627 private void LoadExtensionCore ( string file , string ? proc )
626628 {
627- var rc = sqlite3_load_extension ( Handle , utf8z . FromString ( file ) , utf8z . FromString ( proc ) , out var errmsg ) ;
628- if ( rc != SQLITE_OK )
629+ SqliteException ? firstException = null ;
630+ foreach ( var path in GetLoadExtensionPaths ( file ) )
631+ {
632+ var rc = sqlite3_load_extension ( Handle , utf8z . FromString ( path ) , utf8z . FromString ( proc ) , out var errmsg ) ;
633+ if ( rc == SQLITE_OK )
634+ {
635+ return ;
636+ }
637+
638+ if ( firstException == null )
639+ {
640+ // We store the first exception so that error message looks more obvious if file appears in there
641+ firstException = new SqliteException ( Resources . SqliteNativeError ( rc , errmsg . utf8_to_string ( ) ) , rc , rc ) ;
642+ }
643+ }
644+
645+ if ( firstException != null )
646+ {
647+ throw firstException ;
648+ }
649+ }
650+
651+ private static IEnumerable < string > GetLoadExtensionPaths ( string file )
652+ {
653+ // we always try original input first
654+ yield return file ;
655+
656+ string ? dirName = Path . GetDirectoryName ( file ) ;
657+
658+ // we don't try to guess directories for user, if they pass a path either absolute or relative - they're on their own
659+ if ( ! string . IsNullOrEmpty ( dirName ) )
660+ {
661+ yield break ;
662+ }
663+
664+ bool shouldTryAddingLibPrefix = ! file . StartsWith ( "lib" , StringComparison . Ordinal ) && ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ;
665+
666+ if ( shouldTryAddingLibPrefix )
667+ {
668+ yield return $ "lib{ file } ";
669+ }
670+
671+ NativeDllSearchDirectories ??= GetNativeDllSearchDirectories ( ) ;
672+
673+ foreach ( string dir in NativeDllSearchDirectories )
674+ {
675+ yield return Path . Combine ( dir , file ) ;
676+
677+ if ( shouldTryAddingLibPrefix )
678+ {
679+ yield return Path . Combine ( dir , $ "lib{ file } ") ;
680+ }
681+ }
682+ }
683+
684+ private static string [ ] GetNativeDllSearchDirectories ( )
685+ {
686+ string ? searchDirs = AppContext . GetData ( "NATIVE_DLL_SEARCH_DIRECTORIES" ) as string ;
687+
688+ if ( string . IsNullOrEmpty ( searchDirs ) )
629689 {
630- throw new SqliteException ( Resources . SqliteNativeError ( rc , errmsg . utf8_to_string ( ) ) , rc , rc ) ;
690+ return [ ] ;
631691 }
692+
693+ return searchDirs ! . Split ( [ Path . PathSeparator ] , StringSplitOptions . RemoveEmptyEntries ) ;
632694 }
633695
634696 /// <summary>
0 commit comments