2010-05-25

Merge SQLite to executable file

 Xem bài trước http://gponster.blogspot.com/2010/05/building-sqlitenet-from-source-and.html

Sau một hồi cố merge mix-mode dll vào executable file vẫn không làm được mình đã thử dùng csharp-sqlite http://code.google.com/p/csharp-sqlite/. Tuy nhiên performace của nó khá chậm (khoảng chừng 3-5 lần khi dùng System.Data.SQLite.dll đó là theo cảm giác của mình) với lại mình gặp mới trường hợp báo assert failed có vẻ là không ổn lắm.

Benmark cho small database http://code.google.com/p/csharp-sqlite/wiki/Benchmarks

Jan 20, 2010 Updated to 3.6.22

Small Databases

# RecordsInsertingSearching 2XIteration 2XDeleting
SQLite100,0002.4s3.9s0.4s2.7s
C#-SQLite100,0005.9s4.9s0.5s5.1s
C#/SQLite2.5x1.3x1.0x1.9x

Sau một hồi search trên Google không có cách giải quyết nào khá hơn mình đành về giải pháp đầu tiên mà mình đã nghĩ. Embedded dll vào assembly resource rồi thực hiện load assembly dynamic. Data của SQLite thì cũng build dạng resource rồi thực hiện extract bình thường. Hai cái này khác nhau chút ít trong Build Action là Resource và Embedded Resource.
Để truy cập Embedded Resource dùng GetManifestResourceStream tuy nhiên phải biết chính xác tên của resource. Cách tốt nhất là xem trước tên của resource rồi thực hiện lấy resource.

-->
Assembly assembly = Assembly.GetExecutingAssembly();
string[] names = assembly.GetManifestResourceNames();
foreach (string name in names)
{
    System.Diagnostics.Debug.WriteLine(name);
}


Thêm nữa search trên stackoverflow được method sau, nhìn ok hơn là write trực tiếp ra byte
http://stackoverflow.com/questions/96732/embedding-one-dll-inside-another-as-an-embedded-resource-and-then-calling-it-from/97290#97290
-->
static byte[] StreamToBytes(Stream input)
{
    var capacity = input.CanSeek ? (int)input.Length : 0;

    using (var output = new MemoryStream(capacity))
    {
        int readLength;
        var buffer = new byte[4096];

        do
        {
            readLength = input.Read(buffer, 0, buffer.Length);
            output.Write(buffer, 0, readLength);
        } while (readLength != 0);

        return output.ToArray();
    }
}

Tiếp theo là thực hiện xử lý
-->
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveEventHandler;
 
-->
static Assembly AssemblyResolveEventHandler(object sender, ResolveEventArgs args)
{
    Assembly result = null;

    foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
    {
        if (asm.FullName == args.Name)   //already have
        {
            result = asm;
            break;
        }
    }

    if (result == null && args.Name.Contains("System.Data.SQLite"))
    {
        var prefix = "xxx.yyy."// get full pattern or embebbed reseource
        var name = string.Format("{0}.dll", args.Name.Split(',')[0]);
        var resource = prefix + name;     // DLL have embedded
        var assembly = Assembly.GetExecutingAssembly(); // get assembly

        using (Stream input = assembly.GetManifestResourceStream(resource))
        {
            result = input != null ? LoadLibrary(name, StreamToBytes(input)) : null;
        }
    };

    return result;
}


Prefix xxx.yyy. là phần tên của resource trong phần trước assembly.GetManifestResourceNames() đã biết thông thường là MyAssemblyName.ResourceFolder.

Mới đầu thực hiện load memory sẽ bị lỗi FileLoadException Unverifiable code failed policy check. (Exception from HRESULT: 0x80131402) do SQLite là mix-mode không được build với /clr:safe nên chỉ có các load từ file. Tiếp theo đó khi write ra file với file name lấy từ Path.GetTempFileName() thay vì "System.Data.SQLite.dll" cũng bị báo lỗi tại lúc connect database
Unable to load DLL 'SQLite.Interop.DLL': The specified module could not be found

. (Exception from HRESULT: 0x8007007E)
Don't know nên chuyền thành 
-->
string tempFile = Path.GetTempFileName();
tempFile = Path.Combine(Path.GetDirectoryName(tempFile), "System.Data.SQLite.dll");

Method load library
-->
[DllImport("kernel32.dll")]
private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004;

static void MarkFileToDeleteOnReboot(string fileName)
{
    MoveFileEx(fileName, null, MOVEFILE_DELAY_UNTIL_REBOOT);
}

static Assembly LoadLibrary(string name, byte[] data)
{
    Assembly asm = null;

    try
    {
        asm = Assembly.Load(data);
    }
    catch (FileLoadException e)
    {

        if (e.Message.Contains("HRESULT: 0x80131402"))
        {

            // This is C++/CLI Mixed assembly which can only be loaded from disk, not in-memory
            string tempFile = Path.GetTempFileName();
            tempFile = Path.Combine(Path.GetDirectoryName(tempFile), name);
            File.WriteAllBytes(tempFile, data);
            asm = Assembly.LoadFile(tempFile);
            MarkFileToDeleteOnReboot(tempFile);
        }
        else
        {
            throw; // don't ignore other load errors
        }
    }

    return asm;
}

Phần data của SQLite thực hiện build với Action là Resource sẽ extract trước khi connect như sau:
-->
static string ExtractDataFile(string resource)
{
    string tempPath = Path.GetTempPath();
    string tempFile = Path.Combine(tempPath, Path.Combine("Application define here ... ", resource));
    Directory.CreateDirectory(Path.GetDirectoryName(tempFile));

    StreamResourceInfo sri = Application.GetResourceStream(
        new Uri(string.Format("ResourcesFolder/{0}", resource),
        UriKind.Relative));

    byte[] data = StreamToBytes(sri.Stream);
    sri.Stream.Close();
    sri.Stream.Dispose();

    using (FileStream fs = File.Open(tempFile, FileMode.Create))
    {
        fs.Write(data, 0, data.Length);
        fs.Close();
    }
    MarkFileToDeleteOnReboot(tempFile);
    return tempFile;
}





Các resource khác như file hay ảnh cũng thực hiện tương tự, load helper method như sau
-->
public static T LoadResource<T>(string path)
{
    T c = default(T);
    StreamResourceInfo sri = Application.GetResourceStream(new Uri(path, UriKind.Relative));

    if (sri.ContentType == "application/xaml+xml")
    {
        c = (T)XamlReader.Load(sri.Stream);
    }
    else if (sri.ContentType.IndexOf("image") >= 0)
    {
        BitmapImage bi = new BitmapImage();
        bi.BeginInit();
        bi.StreamSource = sri.Stream;
        bi.EndInit();

        if (typeof(T) == typeof(ImageSource))
        {
            c = (T)((object)bi);
        }
        else if (typeof(T) == typeof(Image))
        {

            Image img = new Image { Source = bi };
            c = (T)((object)img);
        }
    }

    sri.Stream.Close();
    sri.Stream.Dispose();

    return c;
}

No comments:

Post a Comment