如何基于客户端体系结构动态加载SQLite3.DLL的x86 / x64版本?

时间:2012-11-30 22:45:38

标签: .net sqlite pinvoke devart

我正在尝试在运行时动态加载适当的x86 / x64版本的SQLite3.DLL,以便与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后,我之前的代码示例按预期工作。感谢大家的帮助。

3 个答案:

答案 0 :(得分: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();
   }
}

答案 1 :(得分:0)

事实证明,我原来的代码示例确实有效,但我无法在程序的App.Config中正确注册SQLite的DBProviderFactory。我现在可以从应用程序根目录中将x86和x64 sqlite3.dlls捆绑在各自的/ 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

** MyApp **

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

答案 2 :(得分:0)

另一种解决方案是在解决方案中包含适当命名的每种操作系统类型的SQLite3 dll(例如sqlite3-x86.dll和sqlite3-x64.dll),并设置为复制到应用程序的输出目录可执行文件(即将复制到输出目录设置为&#39;始终&#39;)。然后,当程序启动时有一个检查.dll是否存在的函数,如果没有,则确定正在使用哪个OS,然后相应地重命名所需的.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;
            }
        }