一般而言,除单项目且无其它引用的WPF应用(控制台、Winform等其它应用也是如此)(对了,还要除掉dotNET 6的单文件发布🤣),在构建后,运行所需的文件不止一个,通常还会带有几个DLL或其它的文件(如各种媒体文件:mp3、mp4、png等),此时分发起来显然不够方便。然后又在某些情况下我们可能不希望制作安装包,此时Costura.Fody
就可以派上用场了。
Costura.Fody
据此项目的Github页所述,虽然这个项目使用的是最宽松的MIT
开源协议,但其实这居然是个收费项目😂😂😂,这与此前流行的“诚信商店”真是有着异曲同工之妙!
所以…如果我使用了这个项目并且没有贡献至少3美刀就变成失信人了?🤔
对了,等咱有钱了再贡献嘛😁😁
话说回来,Costura.Fody
非常强大,大部分情况下只需要安装这个包就行了。
所以全部的操作就是:在nuget
安装Costura.Fody
到入口项目,非常优雅!
如下图的项目结构,我在SingleFilePackageTest
项目中引用了项目SomeClassLibrary
,在安装了Costura.Fody
后,在生成文件夹单独拷贝SingleFilePackageTest.exe
即可运行。
关于项目中其它资源
将对应资源的生成操作
改为Resource
(注意:不是内容
或嵌入的资源
),
然后我们就可以使用如下代码将其从项目资源中提取出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public static byte[] LoadFileFromResource(string pathInApplication, bool byCurrentAssembly = true) { var assembly = byCurrentAssembly ? Assembly.GetCallingAssembly() : Assembly.GetEntryAssembly(); if (pathInApplication[0] == '/') { pathInApplication = pathInApplication.Substring(1); } var resFilestream = Application.GetResourceStream(new Uri(@"pack://application:,,,/" + assembly.GetName().Name + ";component/" + pathInApplication, UriKind.Absolute)); var ms = new MemoryStream(); resFilestream.Stream.CopyTo(ms); var bytes = ms.ToArray(); return bytes; }
var resourceBytes = LoadFileFromResource("/Assets/Test.mp3", true); Debug.WriteLine($"文件长度:{resourceBytes.Length}");
|
接下来将其保存到临时目录就可以想咋玩就咋玩啦!
对于需要通过DllImport调用的DLL
如果项目中包含需要使用DllImport
调用的dll时,首先通过上述方式将DLL保存到临时目录,再将临时目录追加到当前进程的PATH
环境变量中,然后使用DllImport
才可以正确调用到dll。
上代码:
在使用DllImport
前先调用下面的代码:
1 2
| EmbeddedDllClass.ExtractEmbeddedDlls("MyLib.dll", LoadFileFromResource("/MyLib.dll", true)); EmbeddedDllClass.LoadDll("MyLib.dll");
|
EmbeddedDllClass.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| public class EmbeddedDllClass { private static string tempFolder = ""; public static void ExtractEmbeddedDlls(string dllName, byte[] resourceBytes) { Assembly assem = Assembly.GetExecutingAssembly(); string[] names = assem.GetManifestResourceNames(); AssemblyName an = assem.GetName();
tempFolder = String.Format("{0}.{1}.{2}", an.Name, an.ProcessorArchitecture, an.Version);
string dirName = Path.Combine(Path.GetTempPath(), tempFolder); if (!Directory.Exists(dirName)) { Directory.CreateDirectory(dirName); }
string path = Environment.GetEnvironmentVariable("PATH"); string[] pathPieces = path.Split(';'); bool found = false; foreach (string pathPiece in pathPieces) { if (pathPiece == dirName) { found = true; break; } } if (!found) { Environment.SetEnvironmentVariable("PATH", dirName + ";" + path); }
string dllPath = Path.Combine(dirName, dllName); bool rewrite = true; if (File.Exists(dllPath)) { byte[] existing = File.ReadAllBytes(dllPath); if (resourceBytes.SequenceEqual(existing)) { rewrite = false; } } if (rewrite) { File.WriteAllBytes(dllPath, resourceBytes); } }
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] static extern IntPtr LoadLibrary(string lpFileName);
static public void LoadDll(string dllName) { if (tempFolder == "") { throw new Exception("Please call ExtractEmbeddedDlls before LoadDll"); } IntPtr h = LoadLibrary(dllName); if (h == IntPtr.Zero) { Exception e = new Win32Exception(); throw new DllNotFoundException("Unable to load library: " + dllName + " from " + tempFolder, e); } } }
|
本文部分代码来自StackOverflow@Mark Lakata,感谢!