zoukankan      html  css  js  c++  java
  • 『开源重编译』System.Data.SQLite.dll 自适应 x86 x64 AnyCPU 重编译

    背景:

    > System.Data.SQLite.dll 程序集 不能良好的支持 AngCPU 格式

    System.Data.SQLite.dll 在 适应 x86 和 x64 有三个方案:

    > 分别使用 32 或 64 的 混合编译程序集(程序如果以64位 运行,但引用32位的 程序集 就会报错,反之) —— 所以这种方案 很惹人嫌。

    > 使用 AnyCPU 的程序集 —— 但是 你得间接引用 C++ 核心程序集:SQLite.Interop.dll —— 即:你得 同时引用 两个程序集:System.Data.SQLite.dll  和 SQLite.Interop.dll

    > 第三种基于第二种:运行 SQLite 官方安装文件,将 SQLite.Interop.dll 程序集 自动安装到 系统目录 —— 你调用 AnyCPU版本的 System.Data.SQLite.dll 时,程序会自动去系统目录找对应的 C++程序集。

      (但这种方案 不支持 免安装运行 —— 你本地编译运行正常,复制到 别的电脑上 就崩溃了(目标电脑也得运行 SQLite 官方安装文件))

    纠结就产生了:

    > 我只想引用 一个程序集。

    > 这个程序集 是 AnyCPU  (自动适应 x64 x86)。

    > 我编译好的程序,发给对方,对方就能直接双击运行。

    —— 上面三种方案:基本都无法 完美解决这个问题。

    网上搜索到既有的方案:

    http://download.csdn.net/download/yhbcpg/8441051

    下载之后:

    但是 又有了问题:

    > 这位仁兄 从 SQLite官网 下载代码改造 —— 最后使用了 代码混淆(当然,增加了 几百行代码,保护自己的代码 —— 无可厚非)。

    > 我挺不喜欢 10940_x64 和 10940_86 这两个文件夹的 —— 想基于这个作者修改源码 似乎有点难,算了:干脆自己实现一个了。

    可行性评估尝试:

    > 我从 SQLite官网 下载了 最新的 1.0.105.2 的 源码。

    > 编译之后,手动创建了 x64 x86 目录。

    > 程序正常运行 —— 换言之:SQLite 官方 已经提供了对 x64 x86 文件夹的识别

    > 于是,调整目标:

      > 增加自释放功能。

      > 释放到当前程序目录:不使用 x86 和 x64 目录

      > 释放到对应的平台目录:使用 x86 和 x64 目录

      > 每次运行时 校验C++程序集是否 适应当前程序平台,是否需要重新释放。

    于是,System.Data.SQLite.dll 自适应AnyCPU 自释放 程序集改写 开始:

    > 将 x86 和 x64 的 C++程序集 进行 GZip压缩,节省 程序集字节大小,并内嵌到项目中:

    > 在阅读 SQLite 1.0.105.2 官方源码 过程中,我在源码 UnsafeNativeMethods.cs 中发现了 一个函数: PreLoadSQLiteDll(*)。 

    > 增加了一个 释放C++程序集的 辅助类 __RecoverHelper.cs,直接由 PreLoadSQLiteDll(*) 函数调用。

    > 修改的代码如下:

     1       private static bool PreLoadSQLiteDll(
     2           string baseDirectory,            /* in */
     3           string processorArchitecture,    /* in */
     4           ref string nativeModuleFileName, /* out */
     5           ref IntPtr nativeModuleHandle    /* out */
     6           )
     7       {
     8           //新增的代码
     9           __RecoverHelper.InitResourceSQLiteInteropAssembly();
    10 
    11           //
    12           // NOTE: If the specified base directory is null, use the default
    13           //       (i.e. attempt to automatically detect it).
    14           //
    15           if (baseDirectory == null)
    16               baseDirectory = GetBaseDirectory();
    17 
    18           //.....
    19       }    

    > __RecoverHelper.cs 源码如下:

      1 using System.Configuration;
      2 using System.IO;
      3 using System.IO.Compression;
      4 using System.Reflection;
      5 using System.Security.Cryptography;
      6 using System.Text;
      7 
      8 namespace System.Data.SQLite
      9 {
     10     /// <summary>
     11     /// <para>修复 System.Data.SQLite 所需要的 运行环境.</para>
     12     /// <para>释放 System.Data.SQLite 需要的C++程序集 SQLite.Interop.dll</para>
     13     /// </summary>
     14     internal static class __RecoverHelper
     15     {
     16         #region  尝试加载内嵌程序集
     17 
     18         internal const string SQLite_Interop = @"SQLite.Interop";
     19         internal const string SQLite_Interop_x64MD5 = @"7d40719ca6d7c1622fa54d2f17a97020";
     20         internal const string SQLite_Interop_x86MD5 = @"bfd7e42cd1638debe255771057699574";
     21 
     22 
     23         /// <summary>
     24         /// 是否将 SQLite.Interop.dll 释放到 x64 x86 文件夹, 如果本参数为否 则释放到 当前目录.
     25         /// </summary>
     26         internal static bool InteropPlatformFolder
     27         {
     28             get
     29             {
     30                 string value = (ConfigurationManager.AppSettings["System.Data.SQLite:InteropPlatformFolder"] ?? string.Empty).Trim().ToUpper();
     31                 return (string.IsNullOrEmpty(value) || value == "TRUE" || value == "1" || value == "T");
     32             }
     33         }
     34 
     35 
     36 
     37         /// <summary>
     38         /// 当运行环境 找不到 SQLite.Interop.dll 程序集时, 尝试将 内嵌字节 写回磁盘 还原成原始文件 SQLite.Interop.dll
     39         /// </summary>
     40         internal static void InitResourceSQLiteInteropAssembly()
     41         {
     42             try
     43             {
     44                 //当运行环境 找不到 SQLite.Interop.dll 程序集时, 尝试将 内嵌字节 写回磁盘 还原成原始文件 SQLite.Interop.dll
     45 
     46                 Assembly assembly = Assembly.GetExecutingAssembly();
     47                 string domainFolder = AppDomain.CurrentDomain.BaseDirectory;
     48                 bool is64Proc = (IntPtr.Size == 8);
     49                 string interopFolder = string.Format(@"{0}{1}", domainFolder.TrimEnd('/', '\'), (InteropPlatformFolder ? (is64Proc ? @"x64" : @"x86") : string.Empty));
     50                 string SQLiteInteropDllPath = string.Format(@"{0}{1}.dll", interopFolder.TrimEnd('/', '\'), SQLite_Interop);
     51                 if (!Directory.Exists(interopFolder)) Directory.CreateDirectory(interopFolder);
     52 
     53                 //如果磁盘 SQLite.Interop.dll 存在, 则校验相关 MD5
     54                 if (File.Exists(SQLiteInteropDllPath))
     55                 {
     56                     string existFileMD5 = GetFileMD5(SQLiteInteropDllPath);
     57                     string rightFileMD5 = is64Proc ? SQLite_Interop_x64MD5 : SQLite_Interop_x86MD5;
     58 
     59                     //如果MD5 不一致, 则删除磁盘现有的 SQLite.Interop.dll 
     60                     if (!string.Equals(existFileMD5, rightFileMD5, StringComparison.CurrentCultureIgnoreCase))
     61                         File.Delete(SQLiteInteropDllPath);
     62                 }
     63 
     64 
     65                 //如果磁盘 SQLite.Interop.dll 不存在, 则释放 SQLite.Interop.dll
     66                 if (!File.Exists(SQLiteInteropDllPath))
     67                 {
     68                     string libResourceName = string.Format("{0}.Lib.{1}.x{2}.GZip.dll", Assembly.GetExecutingAssembly().GetName().Name, SQLite_Interop, (is64Proc ? "64" : "86"));
     69                     Stream stream = assembly.GetManifestResourceStream(libResourceName);
     70                     if (stream == null) return;
     71 
     72                     try
     73                     {
     74                         using (stream)
     75                         {
     76                             using (Stream zipStream = (Stream)new GZipStream(stream, CompressionMode.Decompress))
     77                             {
     78                                 using (FileStream myFs = new FileStream(SQLiteInteropDllPath, FileMode.Create, FileAccess.ReadWrite))
     79                                 {
     80                                     //从压缩流中读出所有数据
     81                                     byte[] buffer = new byte[1024];
     82                                     do
     83                                     {
     84                                         int n = zipStream.Read(buffer, 0, buffer.Length);
     85                                         if (n <= 0) break;
     86                                         myFs.Write(buffer, 0, n);
     87                                     } while (true);
     88 
     89                                     zipStream.Close();
     90                                 }
     91                             }
     92                         }
     93                     }
     94                     catch (Exception) { }
     95                 }
     96             }
     97             catch(Exception) { }
     98         }
     99 
    100 
    101         #endregion
    102 
    103         #region  辅助函数
    104 
    105         /// <summary>
    106         /// 计算文件的MD5, 计算错误将返回 空字符串
    107         /// </summary>
    108         public static string GetFileMD5(string path)
    109         {
    110             if (!File.Exists(path)) return string.Empty;
    111 
    112             try
    113             {
    114                 using (FileStream myFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
    115                 {
    116                     using (MD5 md5 = new MD5CryptoServiceProvider())
    117                     {
    118                         byte[] hash = md5.ComputeHash(myFs);
    119                         myFs.Close();
    120 
    121                         StringBuilder sb = new StringBuilder();
    122                         for (int i = 0; i < hash.Length; i++) sb.Append(hash[i].ToString("x2"));
    123                         return sb.ToString();
    124                     }
    125                 }
    126             }
    127             catch (Exception)
    128             {
    129                 return string.Empty;
    130             }
    131         }
    132         /// <summary>
    133         /// 计算指定文件 从指定字节开始 的 指定长度的 文件的MD5 (剩余字节不足, 则只计算剩余字节流), 计算错误将返回 空字符串
    134         /// </summary>
    135         public static string GetFileMD5(string path, long offset, long length)
    136         {
    137             if (!File.Exists(path)) return string.Empty;
    138 
    139             try
    140             {
    141                 const int PACKAGE_SIZE = 1024 * 1024; //每次1M
    142 
    143                 using (MD5 md5 = new MD5CryptoServiceProvider())
    144                 {
    145                     md5.Initialize();
    146 
    147                     using (FileStream myFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
    148                     {
    149                         myFs.Position = offset;
    150                         long fileByteLength = Math.Min(length, myFs.Length - myFs.Position);
    151                         byte[] buffer = new byte[PACKAGE_SIZE];
    152 
    153                         long readLength = 0;
    154                         while (readLength < fileByteLength)
    155                         {
    156                             long leaveLength = myFs.Length - myFs.Position;
    157                             long leaveLength2 = fileByteLength - readLength;
    158                             int bufferLength = (leaveLength > (long)PACKAGE_SIZE) ? PACKAGE_SIZE : Convert.ToInt32(leaveLength);
    159                             bufferLength = (leaveLength2 > (long)bufferLength) ? bufferLength : Convert.ToInt32(leaveLength2);
    160 
    161                             myFs.Read(buffer, 0, bufferLength);
    162 
    163                             if (readLength + bufferLength < fileByteLength)     //不是最后一块
    164                                 md5.TransformBlock(buffer, 0, bufferLength, buffer, 0);
    165                             else                                                //最后一块
    166                                 md5.TransformFinalBlock(buffer, 0, bufferLength);
    167 
    168                             readLength = readLength + bufferLength;
    169                             if (myFs.Position >= myFs.Length) break;
    170                         }
    171 
    172                         byte[] hash = md5.Hash;
    173                         StringBuilder sb = new StringBuilder();
    174                         for (int i = 0; i < hash.Length; i++) sb.Append(hash[i].ToString("x2"));
    175                         return sb.ToString();
    176                     }
    177                 }
    178             }
    179             catch (Exception)
    180             {
    181                 return string.Empty;
    182             }
    183         }
    184 
    185 
    186         #endregion
    187     }
    188 }

    最后使用SQLite官方测试工具测试:

    > 测试通过 如下图:

    相关源码 和 程序集 下载 (使用的是官方密钥签名):

    > 点击下载源码 和 已签名程序集  (如果本文对你有所帮助,麻烦点击一下右下角的 “推荐”,谢谢)

    > 测试时 请直接去 Bin 目录, 运行官方 测试工具 test.exe 和 test32.exe 

    > 默认将 C++ 程序集 释放到 x86 x64 文件夹,如果想设置为:将C++程序集释放到当前目录,可以在 App.config 中 增加如下配置: 

    1 <configuration>
    2   <appSettings>
    3     <add key="System.Data.SQLite:InteropPlatformFolder" value="0"/>
    4 
    5   </appSettings>
    6 </configuration>

  • 相关阅读:
    scrum第二阶段项目冲刺04
    scrum第二阶段项目冲刺03
    scrum第二阶段项目冲刺02
    scrum第二阶段项目冲刺01
    团队项目02——疫情数据分析
    scrum第二阶段项目冲刺
    现阶段本组app与咸鱼的对比
    团队项目第一阶段成果——意见改进
    团队项目第一阶段成果——意见汇总
    文件的合并与分割
  • 原文地址:https://www.cnblogs.com/shuxiaolong/p/System_Data_SQLite_AnyCPU.html
Copyright © 2011-2022 走看看