System.Data.SQLite Redistribution Issues

If you need to use sqlite in .Net application the best binding is, of course, System.Data.SQLite.dll. It is a managed assembly, but unfortunately it requires two versions (32-bit and 64-bit) of unmanaged SQLite.Interop.dll to work properly.

It would be great to embed all 3 DLLs in your application to simplify distribution. This tutorial demonstrates how to achieve this with BoxedApp SDK, a library for virtualizing files and registry.

You can download the sample from GitHub.

Virtualization Helps

BoxedApp SDK emulates files, so the developer creates a virtual file (pseudofile) - a "file" that doesn't exist on a disk, but the process works as if this file actually existed. BoxedApp intercepts file calls to achieve this.

Suppose the application needs some DLLs. You just create virtual files of the DLLs, and the application works.

Embed System.Data.SQLite.dll and SQLite.Interop.dll

To fill a virtual file with data, you need a place where the code takes the data from. For .Net projects, a good choice is embedded resources.

Let's add the folder "EmbeddedResources", place System.Data.SQLite.dll there, then add the folders "x86" and "x64", and place the 32-bit version of SQLite.Interop.dll to x86, and the 64-bit version to x64:

Important: Mark Files as Embedded Resources.

After adding the file to embed, go to their properties and set Build Action as "Embedded Resources".

If you don't do it, GetManifestResourceStream() will fail.

Look how to do this in Visual Studio:

Return to the code. The first thing you need to take care of when BoxedApp SDK in the game is BoxedApp SDK initialization:

/// 
/// The main entry point for the application.
/// 
[STAThread]
static void Main()
{
    // First of all initialize BoxedApp SDK
    BoxedAppSDK.NativeMethods.BoxedAppSDK_Init();
			

Then create 3 virtual files:

// Create virtual files using the data of embedded resources
CreateVirtualFile(Path.Combine(Application.StartupPath, "System.Data.SQLite.dll"), "SqliteEmbeddingSample.EmbeddedResources.System.Data.SQLite.dll");
CreateVirtualFile(Path.Combine(Path.Combine(Application.StartupPath, "x64"), "SQLite.Interop.dll"), "SqliteEmbeddingSample.EmbeddedResources.x64.SQLite.Interop.dll");
CreateVirtualFile(Path.Combine(Path.Combine(Application.StartupPath, "x86"), "SQLite.Interop.dll"), "SqliteEmbeddingSample.EmbeddedResources.x86.SQLite.Interop.dll");
			

The method CreateVirtualFile creates a virtual file using the data from embedded resources.

Firstly it creates a virtual file calling BoxedAppSDK_CreateVirtualFile that returns a virtual file handle. It's a good idea to close it immediately after you got it. You don't need it and closing the handle helps to avoid leaks. SafeFileHandle from Microsoft.Win32.SafeHandles is way to close a handle automatically:

        static void CreateVirtualFile(string virtualFilePath, string embeddedResourceName)
        {
            // Create virtual file and immediate close handle to avoid handle leaks
            using (SafeFileHandle hHandle =
                new SafeFileHandle(
                    BoxedAppSDK.NativeMethods.BoxedAppSDK_CreateVirtualFile(
                        virtualFilePath,
                        BoxedAppSDK.NativeMethods.EFileAccess.GenericWrite,
                        BoxedAppSDK.NativeMethods.EFileShare.Read,
                        IntPtr.Zero,
                        BoxedAppSDK.NativeMethods.ECreationDisposition.New,
                        BoxedAppSDK.NativeMethods.EFileAttributes.Normal,
                        IntPtr.Zero
                    ),
                    true
                )
            )
            {
            }
			

Then CreateVirtualFile opens virtual file, takes stream of the embedded resource and copy all the data from one to another:

            using (Stream embeddedResourceDataStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedResourceName))
            {
                const int bufferSize = 1024;
                byte[] buffer = new byte[bufferSize];

                int readBytes;

                using (FileStream virtualFileStream = new FileStream(virtualFilePath, FileMode.Open))
                {
                    while ((readBytes = embeddedResourceDataStream.Read(buffer, 0, bufferSize)) > 0)
                        virtualFileStream.Write(buffer, 0, readBytes);
                }
            }
			

That's all! When the process asks for System.Data.SQLite.dll and SQLite.Interop.dll, they are provided by BoxedApp SDK.

Download

Sources are available on GitHub.