如何根据客户端架构动态加载x86/x64版本的SQLite3.DLL?

3
我正在尝试在运行时动态加载适用于Devart.SQLite.DLL的x86 / x64版本的SQLite3.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

编辑 如评论中提到的,我未能指明在尝试加载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中,我的之前的代码示例按预期工作。感谢所有人的帮助。


2
你的方法很好。后续的PInvoke调用将使用你选择的库,只要DllImport属性中的DLL名称为“sqlite3”。目前这不是一个真正的问题。你没有提出问题。你没有说哪里出了问题。 - David Heffernan
1
你还应该使用 Path.Combine 来拼接路径组件。 - David Heffernan
@DavidHeffernan 您说得对。我确实没有列出我收到的错误。我收到的错误消息是“无法找到或加载注册的 .Net Framework 数据提供程序。”,结果发现这与 sqlite3.dll 的加载无关,而是一个配置不正确的 App.Config。我还采纳了您的建议,修改了 GetAssemblyPath 程序以使用 Path.Combine。感谢您的帮助! - dthagard
3个回答

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声明。 - David Heffernan
@DavidHeffernan 复制和查找+替换功能非常好用。OP的解决方案有些针对特定平台,因此如果他以后希望将应用移植到mono上,这将成为更好的选择。 - CallumDev
在我看来,复制和粘贴是一个可怕的想法。但我对可避免的重复有一种病态的厌恶感。OP的解决方案就是我所做的。它运行良好。在mono中也可以正常工作。 - David Heffernan
我不明白为什么不行。你能详细说明一下吗?另外,这个问题与Mono无关。 - David Heffernan
是的,那一部分需要用特定于平台的等效物进行替换。 - David Heffernan
显示剩余2条评论

0
另一个解决方案是在您的解决方案中包含适当命名的每个操作系统类型的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;
            }
        }

0
事实证明,我最初得到的代码示例是有效的,但是我没有正确地在程序的App.Config中注册SQLite的DBProviderFactory。现在,我可以在应用程序根目录下的各自/x86和/x64目录中捆绑x86和x64 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>

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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接