『開源重編譯』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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM