using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using NewLife.Log;
namespace ALib
{
/// <summary>
/// DLL资æºç®¡ç†å™¨ - æä¾›é€šç”¨çš„DLL嵌入资æºé‡Šæ”¾åŠŸèƒ½
/// </summary>
public static class DllResourceManager
{
private static readonly Dictionary<string, bool> _loadedDlls = new Dictionary<string, bool>();
private static readonly Dictionary<string, IntPtr> _loadedHandles = new Dictionary<string, IntPtr>();
private static readonly object _lockObject = new object();
/// <summary>
/// 已释放DLLçš„ç»å¯¹è·¯å¾„缓å˜ï¼šæ–‡ä»¶å(å°å†™) -> ç»å¯¹è·¯å¾„。
/// ä¾› DllImport è§£æžå™¨ä½¿ç”¨ï¼Œä¿è¯åŽŸç”Ÿåº“èƒ½è¢«æŒ‰ç»å¯¹è·¯å¾„æ£ç¡®åŠ è½½ã€‚
/// </summary>
private static readonly Dictionary<string, string> _resolvedPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
/// <summary>DllImport è§£æžå™¨æ˜¯å¦å·²æ³¨å†Œï¼ˆåªä¼šæ³¨å†Œä¸€æ¬¡ï¼‰</summary>
private static bool _resolverRegistered;
/// <summary>
/// ç¡®ä¿DLLå¯ç”¨ï¼Œå¦‚æžœä¸å˜åœ¨åˆ™ä»ŽåµŒå…¥èµ„æºä¸é‡Šæ”¾å¹¶æ˜¾å¼åŠ è½½
/// </summary>
/// <param name="resourcePrefix">资æºå‰ç¼€ï¼ˆé€šå¸¸æ˜¯å‘½å空间路径)</param>
/// <param name="dllFileName">DLL文件å</param>
/// <returns>æ˜¯å¦æˆåŠŸ</returns>
public static bool EnsureDllAvailable(string resourcePrefix, string dllFileName)
{
lock (_lockObject)
{
if (_loadedDlls.ContainsKey(dllFileName) && _loadedDlls[dllFileName])
{
return true;
}
try
{
RegisterResolver();
// 1)优先应用目录:Release åŽä¸Žä¸»ç¨‹åºåŒç›®å½•,DllImport 默认æœç´¢é¡ºåºå³å¯å‘½ä¸
// 注æ„ï¼šå•æ–‡ä»¶å‘布时 Assembly.Location 为空,必须用 AppContext.BaseDirectory
var appDir = GetAppDirectory();
if (!string.IsNullOrEmpty(appDir))
{
var appDllPath = Path.Combine(appDir, dllFileName);
if (File.Exists(appDllPath))
{
CacheResolvedPath(dllFileName, appDllPath);
_loadedDlls[dllFileName] = true;
return true;
}
}
// 2)系统路径(应用目录 / PATH)ä¸å·²å˜åœ¨ï¼Œç›´æŽ¥ä½¿ç”¨
var existPath = FindDllInSystem(dllFileName);
if (!string.IsNullOrEmpty(existPath))
{
CacheResolvedPath(dllFileName, existPath);
_loadedDlls[dllFileName] = true;
return true;
}
// 3)从嵌入资æºä¸é‡Šæ”¾ï¼ˆé‡Šæ”¾åˆ°åº”用目录,失败则回退临时目录)
var dllPath = ExtractDllFromResource(resourcePrefix, dllFileName);
CacheResolvedPath(dllFileName, dllPath);
// 显å¼åŠ è½½DLL
return LoadDllExplicitly(dllFileName, dllPath);
}
catch (Exception ex)
{
XTrace.WriteLine($"DLLåŠ è½½å¤±è´¥ {dllFileName}: {ex.Message}");
return false;
}
}
}
/// <summary>
/// 记录已解æžåˆ°çš„DLLç»å¯¹è·¯å¾„,供 DllImport è§£æžå™¨å›žæŸ¥ã€‚
/// </summary>
private static void CacheResolvedPath(string dllFileName, string dllPath)
{
if (string.IsNullOrEmpty(dllPath)) return;
_resolvedPaths[dllFileName] = dllPath;
_resolvedPaths[Path.GetFileName(dllPath)] = dllPath;
}
/// <summary>
/// 为当å‰ç¨‹åºé›†æ³¨å†Œ DllImport è§£æžå™¨ã€‚
/// 啿–‡ä»¶å‘布时原生DLLå¯èƒ½è¢«é‡Šæ”¾åˆ°ä¸´æ—¶ç›®å½•,而 DllImport 默认æœç´¢é¡ºåºä¸åŒ…å«è¯¥ç›®å½•,
/// 通过解æžå™¨æŠŠé€»è¾‘åæ˜ 射到释放åŽçš„ç»å¯¹è·¯å¾„,é¿å… DllNotFoundException。
/// </summary>
private static void RegisterResolver()
{
if (_resolverRegistered) return;
_resolverRegistered = true;
try
{
NativeLibrary.SetDllImportResolver(typeof(DllResourceManager).Assembly, ResolveNativeLibrary);
}
catch (InvalidOperationException)
{
// åŒä¸€ç¨‹åºé›†é‡å¤æ³¨å†Œä¼šæŠ›æ¤å¼‚常,忽略å³å¯
_resolverRegistered = true;
}
catch (Exception ex)
{
XTrace.WriteLine($"注册DllImportè§£æžå™¨å¤±è´¥: {ex.Message}");
}
}
/// <summary>
/// DllImport è§£æžå›žè°ƒï¼šå…ˆæŸ¥ç¼“å˜è·¯å¾„,å†å›žé€€åˆ°é»˜è®¤åŠ è½½é€»è¾‘ã€‚
/// </summary>
private static IntPtr ResolveNativeLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (!string.IsNullOrEmpty(libraryName))
{
string cached;
if (_resolvedPaths.TryGetValue(libraryName, out cached) && File.Exists(cached))
{
if (NativeLibrary.TryLoad(cached, out var handle)) return handle;
}
}
// 回退:按系统默认æœç´¢é¡ºåºæŸ¥æ‰¾
string fallback = libraryName;
if (!string.IsNullOrEmpty(fallback) && NativeLibrary.TryLoad(fallback, assembly, searchPath, out var defaultHandle))
{
return defaultHandle;
}
return IntPtr.Zero;
}
/// <summary>
/// 获å–åº”ç”¨ç›®å½•ã€‚å•æ–‡ä»¶å‘布时 Assembly.Location 为空å—符串,必须回退到 AppContext.BaseDirectory。
/// </summary>
private static string GetAppDirectory()
{
try
{
var location = Assembly.GetExecutingAssembly().Location;
if (!string.IsNullOrEmpty(location))
{
var dir = Path.GetDirectoryName(location);
if (!string.IsNullOrEmpty(dir)) return dir;
}
}
catch { }
// 啿–‡ä»¶å‘布会走到这里:BaseDirectory æŒ‡å‘ exe 所在目录
return AppContext.BaseDirectory;
}
/// <summary>
/// åœ¨ç³»ç»Ÿè·¯å¾„ä¸æŸ¥æ‰¾DLL文件
/// </summary>
private static string FindDllInSystem(string dllFileName)
{
// 检查应用程åºç›®å½•
string appDir = GetAppDirectory();
if (string.IsNullOrEmpty(appDir)) return null;
string dllPathInAppDir = Path.Combine(appDir, dllFileName);
if (File.Exists(dllPathInAppDir))
{
return dllPathInAppDir;
}
// 检查系统PATHä¸çš„目录
string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? Array.Empty<string>();
foreach (string path in paths)
{
if (string.IsNullOrEmpty(path)) continue;
string fullPath = Path.Combine(path.Trim(), dllFileName);
if (File.Exists(fullPath))
{
return fullPath;
}
}
return null;
}
/// <summary>
/// 显å¼åŠ è½½DLL到内å˜
/// </summary>
private static bool LoadDllExplicitly(string dllFileName, string dllPath)
{
try
{
// 如果已ç»åŠ è½½è¿‡ï¼Œå…ˆé‡Šæ”¾
if (_loadedHandles.ContainsKey(dllFileName) && _loadedHandles[dllFileName] != IntPtr.Zero)
{
NativeLibrary.Free(_loadedHandles[dllFileName]);
_loadedHandles.Remove(dllFileName);
}
// 显å¼åŠ è½½DLL
IntPtr handle = NativeLibrary.Load(dllPath);
if (handle == IntPtr.Zero)
{
throw new InvalidOperationException($"æ— æ³•åŠ è½½DLL: {dllPath}");
}
_loadedHandles[dllFileName] = handle;
_loadedDlls[dllFileName] = true;
XTrace.WriteLine($"DLL已显å¼åŠ è½½: {dllPath}");
return true;
}
catch (Exception ex)
{
XTrace.WriteLine($"显å¼åŠ è½½DLL失败 {dllFileName}: {ex.Message}");
return false;
}
}
/// <summary>
/// 从嵌入资æºä¸é‡Šæ”¾DLL。
/// 优先释放到应用目录(DllImport 默认æœç´¢é¡ºåºç¬¬ä¸€æ¥å³å¯å‘½ä¸ï¼‰ï¼›è‹¥åº”用目录ä¸å¯å†™ï¼Œåˆ™å›žé€€åˆ°ä¸´æ—¶ç›®å½•。
/// 支æŒä¸¤ç§åµŒå…¥å½¢æ€ï¼ŒæŒ‰ã€Œzip 优先ã€é€‰æ‹©ï¼š
/// 1)zip 压缩:{å‰ç¼€}.{dllå去扩展å}.zip,内å«åŒå dll,体积å¯å‡å°æ•°å€ï¼ˆæŽ¨è)
/// 2)裸文件:{å‰ç¼€}.{dll文件å}
/// </summary>
private static string ExtractDllFromResource(string resourcePrefix, string dllFileName)
{
var assembly = Assembly.GetExecutingAssembly();
// 约定:zlgcan.dll -> ALib.ZLG_CAN.zlgcan.zip
var baseName = Path.GetFileNameWithoutExtension(dllFileName);
var zipResourceName = $"{resourcePrefix}.{baseName}.zip";
// ä¼˜å…ˆé‡Šæ”¾åˆ°åº”ç”¨ç›®å½•ï¼Œç¡®ä¿ DllImport("xxx.dll") 能被默认æœç´¢è·¯å¾„命ä¸
// (临时目录ä¸åœ¨åŽŸç”ŸDLL默认æœç´¢è·¯å¾„ä¸ï¼Œä¼šå¯¼è‡´ DllImport è¿è¡Œæ—¶æ‰¾ä¸åˆ°åº“而抛 DllNotFoundException)
var appDir = GetAppDirectory();
if (!string.IsNullOrEmpty(appDir))
{
var appDllPath = Path.Combine(appDir, dllFileName);
if (TryExtract(assembly, zipResourceName, $"{resourcePrefix}.{dllFileName}", appDllPath))
{
XTrace.WriteLine($"DLL已释放到: {appDllPath}");
return appDllPath;
}
}
// 回退:应用目录ä¸å¯å†™ï¼ˆå¦‚啿–‡ä»¶æ”¾åœ¨ Program Filesã€åªè¯»ç›˜ï¼‰æ—¶é‡Šæ”¾åˆ°ä¸´æ—¶ç›®å½•。
// 释放到 Temp åŽå¿…é¡»ä¾èµ–已注册的 DllImport è§£æžå™¨æŒ‰ç»å¯¹è·¯å¾„åŠ è½½ï¼Œå¦åˆ™ DllImport 找ä¸åˆ°åº“。
var tempDir = Path.Combine(Path.GetTempPath(), "ALib.Native");
string tempDllPath = Path.Combine(tempDir, dllFileName);
if (!TryExtract(assembly, zipResourceName, $"{resourcePrefix}.{dllFileName}", tempDllPath))
{
throw new InvalidOperationException($"æ— æ³•é‡Šæ”¾åµŒå…¥èµ„æº: {resourcePrefix}.{dllFileName} 或 {zipResourceName}");
}
// 进程退出时清ç†ä¸´æ—¶ç›®å½•内本次释放的文件
var pathToDelete = tempDllPath;
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
{
try { if (File.Exists(pathToDelete)) File.Delete(pathToDelete); } catch { }
};
XTrace.WriteLine($"DLL已释放到: {tempDllPath}");
return tempDllPath;
}
/// <summary>
/// 释放å•个 DLL:优先å°è¯• zip 资æºï¼Œä¸å˜åœ¨æˆ–失败则回退裸文件资æºã€‚
/// </summary>
/// <param name="assembly">程åºé›†</param>
/// <param name="zipResourceName">zip 资æºå(å¯ä¸å˜åœ¨ï¼‰</param>
/// <param name="rawResourceName">裸文件资æºå(å¯ä¸å˜åœ¨ï¼‰</param>
/// <param name="destPath">ç›®æ ‡è·¯å¾„</param>
/// <returns>æ˜¯å¦æˆåŠŸ</returns>
private static bool TryExtract(Assembly assembly, string zipResourceName, string rawResourceName, string destPath)
{
// ç›®æ ‡å·²å˜åœ¨ï¼ˆä¸”éžæœ¬æ¬¡å†™å…¥ï¼‰æ—¶ç›´æŽ¥å¤ç”¨ï¼Œé¿å…文件被å 用导致写入失败
if (File.Exists(destPath) && new FileInfo(destPath).Length > 0) return true;
// 1)zip å½¢å¼
if (!string.IsNullOrEmpty(zipResourceName) &&
assembly.GetManifestResourceStream(zipResourceName) != null)
{
if (ZipResourceExtractor.ExtractFile(zipResourceName, destPath)) return true;
XTrace.WriteLine($"zip 释放失败,å°è¯•裸文件资æº: {zipResourceName}");
}
// 2)裸文件形å¼
return TryExtractTo(assembly, rawResourceName, destPath);
}
/// <summary>将嵌入资æºå†™å…¥æŒ‡å®šè·¯å¾„,æˆåŠŸè¿”å›ž true</summary>
private static bool TryExtractTo(Assembly assembly, string resourceName, string destPath)
{
try
{
// ç›®æ ‡å·²å˜åœ¨ï¼ˆä¸”éžæœ¬æ¬¡å†™å…¥ï¼‰æ—¶ç›´æŽ¥å¤ç”¨ï¼Œé¿å…文件被å 用导致写入失败
if (File.Exists(destPath)) return true;
using Stream stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
throw new InvalidOperationException($"æ— æ³•æ‰¾åˆ°åµŒå…¥èµ„æº: {resourceName}");
}
// ç¡®ä¿ç›®å½•å˜åœ¨
var dir = Path.GetDirectoryName(destPath);
if (!String.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
// 写临时文件åŽåŽŸåæ›¿æ¢ï¼Œé¿å…å¹¶å‘/å 用
var tmp = destPath + ".tmp";
using (var fileStream = new FileStream(tmp, FileMode.Create, FileAccess.Write, FileShare.None))
{
stream.CopyTo(fileStream);
fileStream.Flush();
}
File.Move(tmp, destPath, true);
return true;
}
catch (Exception ex)
{
XTrace.WriteLine($"释放 {Path.GetFileName(destPath)} 失败: {ex.Message}");
return false;
}
}
/// <summary>
/// é‡ç½®DLLåŠ è½½çŠ¶æ€ï¼ˆä¸»è¦ç”¨äºŽæµ‹è¯•æˆ–é‡æ–°åŠ è½½ï¼‰
/// </summary>
public static void ResetLoadStatus(string dllFileName = null)
{
lock (_lockObject)
{
if (dllFileName != null)
{
if (_loadedHandles.ContainsKey(dllFileName))
{
NativeLibrary.Free(_loadedHandles[dllFileName]);
_loadedHandles.Remove(dllFileName);
}
_loadedDlls.Remove(dllFileName);
}
else
{
// é‡Šæ”¾æ‰€æœ‰å¥æŸ„
foreach (var handle in _loadedHandles.Values)
{
if (handle != IntPtr.Zero)
{
NativeLibrary.Free(handle);
}
}
_loadedHandles.Clear();
_loadedDlls.Clear();
}
}
}
/// <summary>
/// 程åºé€€å‡ºæ—¶æ¸…ç†èµ„æºï¼ˆå¯é€‰ï¼‰
/// </summary>
public static void Cleanup()
{
ResetLoadStatus();
}
}
}
|