feat: 增强便携版支持与配置管理

- 更新 .gitignore 文件,添加便携版分发目录和 Fody 生成的配置文件。
- 修改 README.md,详细说明便携版的构建与分发流程,强调单文件分发的优势。
- 在 Config.cs 中实现便携配置管理,优先使用 exe 同目录的 config.json,提升用户体验。
- 更新 ExcelService.cs,优化默认模板的导出逻辑,支持现场替换模板文件。
- 在 MainForm.cs 中加载应用图标,提升界面一致性。
- 调整 Program.cs,确保在单文件分发时正确加载 native DLL。
This commit is contained in:
chenshoulin
2026-11-01 20:17:56 +08:00
parent afed57047a
commit d055b415fa
17 changed files with 965 additions and 20 deletions
+83
View File
@@ -0,0 +1,83 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
namespace TfsExcelImporter
{
/// <summary>
/// TFS Client OM 除托管程序集外还依赖两个纯 win32 DLL
/// (Microsoft.WITDataStore32/64.dll)。托管程序集由 Costura 内嵌并从内存加载,
/// native DLL 只能落盘后 LoadLibrary,所以单文件分发时:
/// 启动阶段把内嵌的 native DLL 释放到 %TEMP%\TfsExcelImporter\native\{内容hash},
/// 再 LoadLibrary 预加载 —— 之后 TFS 内部的 DllImport 会直接命中已加载模块。
/// 目录名带 DLL 内容 hash,升级后不会残留旧版本。
/// exe 同目录存在同名 DLL(开发机 bin 目录、绿色解压版)时不做任何处理。
/// </summary>
internal static class NativeLibraryLoader
{
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryW")]
private static extern IntPtr LoadLibrary(string path);
public static void EnsureLoaded()
{
var fileName = Environment.Is64BitProcess
? "Microsoft.WITDataStore64.dll"
: "Microsoft.WITDataStore32.dll";
if (File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName)))
return;
try
{
var asm = typeof(NativeLibraryLoader).Assembly;
var resourceName = "native." + fileName;
using (var stream = asm.GetManifestResourceStream(resourceName))
{
if (stream == null)
{
Debug.WriteLine("未找到内嵌 native 资源: " + resourceName);
return;
}
byte[] bytes;
using (var buffer = new MemoryStream())
{
stream.CopyTo(buffer);
bytes = buffer.ToArray();
}
var target = Path.Combine(NativeDir(bytes), fileName);
Directory.CreateDirectory(Path.GetDirectoryName(target));
if (!File.Exists(target))
{
try { File.WriteAllBytes(target, bytes); }
catch (IOException)
{
// 同 hash 目录已被另一实例写住,内容等价,直接用
if (!File.Exists(target)) throw;
}
}
if (LoadLibrary(target) == IntPtr.Zero)
Debug.WriteLine("LoadLibrary 失败(" + Marshal.GetLastWin32Error() + "): " + target);
}
}
catch (Exception ex)
{
// 释放失败不阻断启动:TFS 连接时会自行报 DllNotFound,提示仍是可读的
Debug.WriteLine("释放 native DLL 失败: " + ex.Message);
}
}
private static string NativeDir(byte[] bytes)
{
byte[] hash;
using (var sha = SHA256.Create())
hash = sha.ComputeHash(bytes);
var stamp = BitConverter.ToString(hash, 0, 4).Replace("-", "");
return Path.Combine(Path.GetTempPath(), "TfsExcelImporter", "native", stamp);
}
}
}