3

I'm trying to dynamically load the appropriate x86/x64 version of the SQLite3.DLL at runtime for use with the Devart.SQLite.DLL. I don't have control over installing the appropriate version of the DLL to the application root beforehand, so I must somehow try and get the correct version from either a /x86 or /x64 subdirectory from the application root.

Any ideas on how to accomplish this? Admittedly, I'm completely lost here. My code thus far is:

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

EDIT As mentioned in the comments, I failed to specify what the actual error I was receiving when trying to load the sqlite3.dll. As it turns out, I was missing the following in my 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>

Once I added this to the App.Config, my previous code sample worked as expected. Thank everyone for their help.

dthagard
  • 823
  • 7
  • 23
  • 2
    Your approach is fine. Subsequent pinvoke calls will use the library that you selected, so long as the DLL name in the DllImport attribute is "sqlite3".dll. At the moment this is not a real question. You didn't ask a question. You didn't say what is failing. – David Heffernan Dec 01 '12 at 08:29
  • 1
    You should also use Path.Combine to piece together path components – David Heffernan Dec 01 '12 at 08:31
  • @DavidHeffernan you are correct. I did fail to list what error I was receiving. The error message I got was "Failed to find or load the registered .Net Framework Data Provider." which as it turns out was not related to the loading of the sqlite3.dll but rather a misconfigured App.Config. I also took you advice and modified the GetAssemblyPath routine to use Path.Combine. Thanks for your help! – dthagard Dec 01 '12 at 22:48

3 Answers3

1

You could create an interface with two implementations. An x86 implementation and an x64 implementation. One could say [DllImport("x86version.dll")]Bob(string s); and one could say [DllImport("x64version.dll")]Bob(string s);

Example:

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();
   }
}
CallumDev
  • 239
  • 1
  • 9
0

As it turns out, the original code sample I had did work, but I failed to properly register the DBProviderFactory for SQLite in my program's App.Config. I now can bundle the x86 and x64 sqlite3.dlls in their respective /x86 and /x64 directory from the application root at have the proper version loaded at runtime. For those looking for a full solution, see below (thanks everyone for your code improvements; they have been incorporated):

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>
dthagard
  • 823
  • 7
  • 23
0

Another solution to this would be to include both the SQLite3 dlls for each OS type named appropriately (e.g. sqlite3-x86.dll and sqlite3-x64.dll) in your solution and set to copy to the output directory of the application with the executable (i.e. set Copy to Output Directory to 'Always'). Then, when the program starts have a function that checks if the .dll is there, and if not, determine which OS is being used, then rename the required .dll accordingly. This would then only be done once to name the correct .dll. There would be no requirement to dynamically load a .dll. This is the code I use:

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;
            }
        }
dereknash
  • 155
  • 1
  • 1
  • 6