背景:
> 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>