Skip to content

Commit 59fc95b

Browse files
authored
Improve LoadExtension to work correctly with dotnet run and lib* named libs (#35617)
* Improve LoadExtension to work correctly with dotnet run and lib packages * Use [] instead of Array.Empty
1 parent ec9ade3 commit 59fc95b

File tree

1 file changed

+65
-3
lines changed

1 file changed

+65
-3
lines changed

Diff for: src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs

+65-3
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)