【问题标题】:How Does One Dynamically Load x86/x64 Version of SQLite3.DLL Based On Client Architecture?如何基于客户端架构动态加载x86/x64版本的SQLite3.DLL?
【发布时间】:2025-12-04 03:05:02
【问题描述】:

我正在尝试在运行时动态加载 SQLite3.DLL 的适当 x86/x64 版本,以便与 Devart.SQLite.DLL 一起使用。我无法控制事先将适当版本的 DLL 安装到应用程序根目录,因此我必须以某种方式尝试从应用程序根目录的 /x86 或 /x64 子目录中获取正确的版本。

关于如何做到这一点的任何想法?诚然,我在这里完全迷失了。到目前为止,我的代码是:

Public Sub New()
    LoadAssembly("sqlite3.dll", True)
End Sub

Private Function GetAssemblyPath(ByVal assembly As String, ByVal version As String) As String
    Return Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\" & version & "\" & assembly
End Function ' GetAssemblyName

<Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint:="LoadLibraryW")> _
Public Shared Function LoadLibraryW(<Runtime.InteropServices.InAttribute()> <Runtime.InteropServices.MarshalAsAttribute(Runtime.InteropServices.UnmanagedType.LPWStr)> lpLibFileName As String) As IntPtr
End Function

Private Sub LoadAssembly(ByVal myAssembly As String, Optional ByVal doLoadLibrary As Boolean = False)
    Dim an As AssemblyName
    Dim filename As String
    Dim version As String

    If UIntPtr.Size = 8 Then version = "x64" Else version = "x86"
    filename = GetAssemblyPath(myAssembly, version)

    Try
        If doLoadLibrary Then
            HostLog.WriteEntry(filename, EventLogEntryType.Information)
            Dim ptr As IntPtr = LoadLibraryW(filename)
            HostLog.WriteEntry(ptr.ToString(), EventLogEntryType.Information)
        Else
            an = AssemblyName.GetAssemblyName(filename)
            AppDomain.CurrentDomain.Load(an)
        End If
    Catch ex As Exception
        HostLog.WriteEntry(ex.Message, EventLogEntryType.Error)
    End Try
End Sub ' LoadAssembly

编辑 如 cmets 中所述,我未能指定在尝试加载 sqlite3.dll 时收到的实际错误。事实证明,我的 App.Config 中缺少以下内容:

<system.data>
  <DbProviderFactories>
    <remove invariant="Devart.Data.SQLite" />
    <add name="dotConnect for SQLite" 
       invariant="Devart.Data.SQLite" 
       description="Devart dotConnect for SQLite" 
       type="Devart.Data.SQLite.SQLiteProviderFactory, Devart.Data.SQLite, Version=4.2.122.0, Culture=neutral, PublicKeyToken=09af7300eec23701" />
  </DbProviderFactories>
</system.data>

将其添加到 App.Config 后,我之前的代码示例按预期工作。感谢大家的帮助。

【问题讨论】:

  • 你的方法很好。后续 pinvoke 调用将使用您选择的库,只要 DllImport 属性中的 DLL 名称是“sqlite3”.dll。目前这不是一个真正的问题。你没有问一个问题。你没有说什么是失败的。
  • 你也应该使用 Path.Combine 来拼凑路径组件
  • @DavidHeffernan 你是对的。我确实没有列出我收到的错误。我收到的错误消息是“无法找到或加载已注册的 .Net Framework 数据提供程序”。事实证明,这与 sqlite3.dll 的加载无关,而是与配置错误的 App.Config 有关。我还听取了您的建议并修改了 GetAssemblyPath 例程以使用 Path.Combine。感谢您的帮助!

标签: .net sqlite pinvoke devart


【解决方案1】:

您可以创建一个具有两个实现的接口。一个 x86 实现和一个 x64 实现。可以说[DllImport("x86version.dll")]Bob(string s);,也可以说[DllImport("x64version.dll")]Bob(string s);

例子:

public interface ISQLite
{
    public void Foo();
}

public class SQLite32 : ISQLite
{
   [DllImport("x86/SQLite3.dll")]
    private void foo();
   public void Foo()
   {
       foo();
   }
}

public class SQLite64 : ISQLite
{
   [DllImport("x64/SQLite3.dll")]
    private void foo();
   public void Foo()
   {
      foo();
   }
}

public static class SQLiteLoader
{
   public static ISQLite GetSQLite()
   {
       if(System.Environment.Is64BitOperatingSystem)
          return new SQLite64();
       else
          return new SQLite32();
   }
}

【讨论】:

  • DLL 都具有相同的名称,并且 OP 试图避免将所有 p/invoke 声明写入两次
  • @DavidHeffernan 复制和查找+替换创造奇迹。 OP 的解决方案是一个更具体的平台,所以如果他希望稍后将他的应用程序移动到单声道,这将是一个更好的选择。
  • 在我看来,复制和粘贴是一个可怕的想法。但我对可避免的重复有着病态的仇恨。 OP的解决方案就是我所做的。它运作良好。它也可以在单声道中正常工作。
  • 如果 OP 希望将他的程序移植到另一个平台(因此使用单声道),那么该解决方案将不起作用
  • 我不明白为什么不这样做。你能详细说明为什么吗?此外,问题与单声道无关。
【解决方案2】:

事实证明,我的原始代码示例确实有效,但我未能在我的程序的 App.Config 中正确注册 SQLite 的 DBProviderFactory。我现在可以将 x86 和 x64 sqlite3.dll 从应用程序根目录捆绑到它们各自的 /x86 和 /x64 目录中,并在运行时加载正确的版本。对于那些正在寻找完整解决方案的人,请参见下文(感谢大家对代码的改进;它们已被合并):

App.Config

<system.data>
  <DbProviderFactories>
    <remove invariant="Devart.Data.SQLite" />
    <add name="dotConnect for SQLite" 
       invariant="Devart.Data.SQLite" 
       description="Devart dotConnect for SQLite" 
       type="Devart.Data.SQLite.SQLiteProviderFactory, Devart.Data.SQLite, Version=4.2.122.0, Culture=neutral, PublicKeyToken=09af7300eec23701" />
  </DbProviderFactories>
</system.data>

AssemblyLoader.vb

Imports System.IO
Imports System.Reflection
Imports System.Security

''' <summary>
''' Handles dynamically loading managed and unmanaged assemblies.
''' </summary>
Public Class AssemblyLoader

  ''' <summary>
  ''' Loads the appropriate x86/x64 version of an assembly based on its filename.
  ''' </summary>
  ''' <param name="myAssembly">The filename of the assembly.</param>
  ''' <param name="isManaged">True if the assembly is managed, otherwise False.</param>
  ''' <exception cref="ArgumentException">If myAssembly is invalid, such as an assembly with an invalid culture.</exception>
  ''' <exception cref="SecurityException">The caller does not have path discovery permission.</exception>
  ''' <exception cref="BadImageFormatException">Thrown if myAssembly is not a valid assembly. -or-Version 2.0 or later of the common language runtime is currently loaded and assemblyRef was compiled with a later version.</exception>
  ''' <exception cref="FileLoadException">An assembly or module was loaded twice with two different evidences.</exception>
  ''' <exception cref="AppDomainUnloadedException">The operation is attempted on an unloaded application domain.</exception>
  Public Shared Sub LoadByVersion(ByVal myAssembly As String, Optional ByVal isManaged As Boolean = True)
    Dim an As AssemblyName
    Dim filename As String
    Dim version As String
    If UIntPtr.Size = 8 Then version = "x64" Else version = "x86"
    filename = GetAssemblyPath(myAssembly, version)

    Try
        If Not File.Exists(filename) Then Exit Sub
        If isManaged Then
            an = AssemblyName.GetAssemblyName(filename)
            If Not IsNothing(an) Then AppDomain.CurrentDomain.Load(an)
        Else
            LoadLibraryW(filename)
        End If
    Catch ex As ArgumentException
        Throw
    Catch ex As SecurityException
        Throw
    Catch ex As BadImageFormatException
        Throw
    Catch ex As FileLoadException
        Throw
    Catch ex As AppDomainUnloadedException
        Throw
    End Try
  End Sub ' LoadAssembly

  ''' <summary>
  ''' Gets the absolute path of the dependant assembly.
  ''' </summary>
  ''' <param name="assembly">The filename (without path) of the dependent assembly.</param>
  ''' <param name="version">The subfolder containing the version of the assembly needed (e.g. "x86", "x64").</param>
  ''' <returns>The absolute path of the specific version of the assembly.</returns>
  Private Shared Function GetAssemblyPath(ByVal assembly As String, ByVal version As String) As String
    Return Path.Combine(Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location), version, assembly)
  End Function ' GetAssemblyName

  ''' Return Type: HMODULE->HINSTANCE->HINSTANCE__*
  '''lpLibFileName: LPCWSTR->WCHAR*
  <Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint:="LoadLibraryW")> _
  Private Shared Function LoadLibraryW(<Runtime.InteropServices.InAttribute()> <Runtime.InteropServices.MarshalAsAttribute(Runtime.InteropServices.UnmanagedType.LPWStr)> lpLibFileName As String) As IntPtr
  End Function ' LoadLibraryW

End Class ' AssemblyLoader

** 我的应用程序 **

<snip>
Public Sub New()
    Try
        AssemblyLoader.LoadByVersion("sqlite3.dll", False)
    Catch ex As Exception
        ' Error logging done here
    End Try
End Sub
</snip>

【讨论】:

    【解决方案3】:

    对此的另一种解决方案是在您的解决方案中包含适当命名的每种操作系统类型的 SQLite3 dll(例如 sqlite3-x86.dll 和 sqlite3-x64.dll),并设置为复制到应用程序的输出目录可执行文件(即将复制到输出目录设置为“始终”)。然后,当程序启动时,有一个函数会检查 .dll 是否存在,如果不存在,则确定正在使用的操作系统,然后相应地重命名所需的 .dll。然后只需执行一次即可命名正确的 .dll。不需要动态加载 .dll。这是我使用的代码:

    public static bool checkForSQLite()
    {
          string sqliteFileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "sqlite3.dll");
    
                if (!File.Exists(sqliteFileName))
                {
                    string version = "x86";
    
                    if (IntPtr.Size == 8)
                    {
                        version = "x64";
                    }
    
                    string resourceFileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "sqlite3-" + version + ".dll");
    
                    File.Move(resourceFileName, sqliteFileName);
    
                    return true;
                }
                else
                {
                    return false;
                }
            }
    

    【讨论】: