3

我试图在运行时动态加载 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

编辑 正如评论中提到的,我没有指定我在尝试加载 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 后,我之前的代码示例按预期工作。感谢大家的帮助。

4

3 回答 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();
   }
}
于 2012-12-01T08:32:43.167 回答
0

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

应用程序配置

<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>

程序集加载器.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>
于 2012-12-01T23:06:41.957 回答
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;
            }
        }
于 2015-01-13T14:46:43.373 回答