Embedding Localization Resources .DLL's to the Executable in C#? - c#

I want to make my program multilingual. I have successfully made the program multilingual via Form's Localizable and Language properties. It made some .resx files. Then I deleted non-needed files such as images (which they are the same in all langauges) etc from the .resx files.
The problem is, for example, it also generates a folder called "en" and in that folder, another generated file is called "ProjectName.resources.dll".
Is there anyway to embed this resource file to the .exe? Adding it to the resources and setting Build Action to "Embedded Resource" doesn't work also.
Thanks.

In .NET Framework 4 you can embed resource library into executable.
http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx
Just create same structure ( with localized folders 'lib/en', 'lib/de' ) and embed them.
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args) {
AssemblyName MissingAssembly = new AssemblyName(args.Name);
CultureInfo ci = MissingAssembly.CultureInfo;
...
resourceName = "MyApp.lib." + ci.Name.Replace("-","_") + "." + MissingAssembly.Name + ".dll";
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)
...
}

You've asked this question a while ago and you've already accepted an answer, but still I'll try to provide an alternative way. I had the same problem and this is how I solved it:
I added the dll as a Ressource to my C#-Project and added this code to my Main-Method (the one that starts your main winform).
public static void Main(string[] args)
{
if (InitdeDEDll()) // Create dll if it's missing.
{
// Restart the application if the language-package was added
Application.Restart();
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new YOURMAINFORM());
}
private static bool InitdeDEDll() // Initialize the German de-DE DLL
{
try
{
// Language of my package. This will be the name of the subfolder.
string language = "de-DE";
return TryCreateFileFromRessource(language, #"NAMEOFYOURDLL.dll",
NAMESPACEOFYOURRESSOURCE.NAMEOFYOURDLLINRESSOURCEFILE);
}
catch (Exception)
{
return false;
}
}
private static bool TryCreateFileFromRessource(string subfolder, string fileName, byte[] buffer)
{
try
{
// path of the subfolder
string subfolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + (subfolder != "" ? #"\" : "") + subfolder;
// Create subfolder if it doesn't exist
if (!Directory.Exists(subfolder))
Directory.CreateDirectory(subfolderPath);
fileName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + #"\" + subfolder + (subfolder!=""?#"\":"") + fileName;
if (!File.Exists(fileName)) // if the dll doesn't already exist, it has to be created
{
// Write dll
Stream stream = File.Create(fileName);
stream.Write(buffer, 0, buffer.GetLength(0));
stream.Close();
}
else
{
return false;
}
}
catch
{
return false;
}
return true;
}
}
Note: This will create the folder and language-dll again if it's missing, so you don't have to care anymore that you copy that folder and the dll with your exe-file. If you want it to be completely vanished this won't be the right approach of course.

Related

File.Exists not working in unity

I'm trying to write a script so the text of a save button changes if it has found a save file in that slot. However, my script cannot seem to find the file no matter what I do. Here's what I have right now.
if (File.Exists ("save1.dat")) {
//set button text
} else {
Debug.Log ("Failure");
}
I've tried multiple different variations on this. I've tried it with different files, different file extensions, including the Application.dataPath, using Resources.Load, but nothing works. For some reason, these functions cannot seem to find any files in my unity project, despite the fact I can see them clearly in both the unity editor and my file explorer. What are some reasons this might be happening? Are there ways to circumvent this?
The file path you are asking for is not a valid file path. You need to use the Application.dataPath as a root directory and make sure that it ends in a / before appending a file. You may also have to replace \ with / (looking at my own code).
This is sort of a hodgepodge, but I use this to determine the application directory for file IO:
public static class Configuration {
public static string currentBaseDirectory = "";
public static string currentDirectory = "";
public static void loadCurrentDirectory ()
{
currentDirectory = Application.dataPath;
currentDirectory = currentDirectory.Replace( #"\", "/" );
bool hasFoundMatch = false;
if ( !currentDirectory.EndsWith( "/" ) )
currentDirectory += "/";
switch (Application.platform) {
case RuntimePlatform.OSXEditor: //<path to project folder>/Assets
case RuntimePlatform.WindowsEditor:
if(currentDirectory.EndsWith("Assets/")) {
currentDirectory = currentDirectory.Substring(0, currentDirectory.LastIndexOf( "Assets/" ) );
currentDirectory += "RuntimeData/";
hasFoundMatch = true;
}
break;
case RuntimePlatform.WindowsPlayer: //<path to executablename_Data folder>
break;
case RuntimePlatform.OSXPlayer: //<path to player app bundle>/Contents
if(currentDirectory.EndsWith(".app/Contents/")) {
currentDirectory = currentDirectory.Substring(0, currentDirectory.LastIndexOf( ".app/Contents/" ) );
currentDirectory += "RuntimeData/";
hasFoundMatch = true;
}
break;
case RuntimePlatform.OSXDashboardPlayer: //<path to the dashboard widget bundle>
case RuntimePlatform.WindowsWebPlayer: //not supported
case RuntimePlatform.OSXWebPlayer:
default:
hasFoundMatch = false;
break;
}
if (!hasFoundMatch) {
currentDirectory = Path.GetFullPath("RuntimeData/");
currentDirectory = currentDirectory.Replace(#"\", "/");
}
if (!Directory.Exists( currentDirectory)) {
for (int i = 0; i < 3; i++)
currentDirectory = currentDirectory.Substring( 0, currentDirectory.LastIndexOf( "/" ) );
currentDirectory += "/RuntimeData/";
}
currentBaseDirectory = currentDirectory.Replace("/RuntimeData", "");
}
}
This allows me to have a RuntimeData directory next to Assets that I can put things like save files in. This folder then ships with the executable when published (although you might want a clean copy, free from any dev testing saves ;) ).

Programmatically extract the .dlls from clickonce application .deploy files

I have a click once application which is deployed on server (network share), is there a way programmatically that extract the .dlls from the .dll.deploy files. Once we launch the application, it converts into dlls and places in user/appdata/local/... folder, but I need a way to extract the dlls without launching the application.
Thanks for the help.
Simply rename the files by omitting the .deploy suffix and you're done.
Since this task could get a little tedious - depending on the number of files in your ClickOnce package - you might want to use a file renaming tool.
Update:
I just noticed that you were asking for a way to do this programmatically. That makes it even easier since you don't have to worry about finding a file renaming tool:
public class Program
{
private const string SourcePath = "\\\\ClickOnceDeployDir";
private const string TargetPath = "C:\\Users\\UserName\\Documents\\ClickOnceTargetDir";
public static void Main(string[] args)
{
var sourceDirectory = new DirectoryInfo(SourcePath);
var targetDirectory = new DirectoryInfo(TargetPath);
// you can omit this step if you would like to do the renaming in-place
Copy(sourceDirectory, targetDirectory);
foreach (var file in targetDirectory.GetFiles("*.deploy", SearchOption.AllDirectories))
{
var directoryName = file.DirectoryName;
if (directoryName != null)
{
// here it is: rename the file
file.MoveTo(Path.Combine(directoryName, Path.GetFileNameWithoutExtension(file.Name)));
}
}
}
private static void Copy(DirectoryInfo sourceDirectory, DirectoryInfo targetDirectory)
{
foreach (var file in sourceDirectory.GetFiles())
{
file.CopyTo(Path.Combine(targetDirectory.FullName, file.Name));
}
foreach (var directory in sourceDirectory.GetDirectories())
{
Copy(directory, targetDirectory.CreateSubdirectory(directory.Name));
}
}
}

Shadow copy with AppDomain to overwrite exe at runtime

In the following sample app I create a new AppDomain and I execute it with shadow copy enabled. From the new AppDomain I then try to delete (replace) the original main exe. However I get an "access is denied error". Interestingly, after launching the program, from Windows Explorer it is possible to rename the main exe (but not to delete it).
Can shadow copy work for runtime overwriting of the main exe?
static void Main(string[] args)
{
// enable comments if you wanna try to overwrite the original exe (with a
// copy of itself made in the default AppDomain) instead of deleting it
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
{
Console.WriteLine("I'm the default domain");
System.Reflection.Assembly currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
string startupPath = currentAssembly.Location;
//if (!File.Exists(startupPath + ".copy"))
// File.Copy(startupPath, startupPath + ".copy");
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = Path.GetFileName(startupPath);
setup.ShadowCopyFiles = "true";
AppDomain domain = AppDomain.CreateDomain(setup.ApplicationName, AppDomain.CurrentDomain.Evidence, setup);
domain.SetData("APPPATH", startupPath);
domain.ExecuteAssembly(setup.ApplicationName, args);
return;
}
Console.WriteLine("I'm the created domain");
Console.WriteLine("Replacing main exe. Press any key to continue");
Console.ReadLine();
string mainExePath = (string)AppDomain.CurrentDomain.GetData("APPPATH");
//string copyPath = mainExePath + ".copy";
try
{
File.Delete(mainExePath );
//File.Copy(copyPath, mainExePath );
}
catch (Exception ex)
{
Console.WriteLine("Error! " + ex.Message);
Console.ReadLine();
return;
}
Console.WriteLine("Succesfull!");
Console.ReadLine();
}
You can achive self updating application within a single application with multiple AppDomains. The trick is move application executable to a temporary directory and copy back to your directory, then load the copied executable in a new AppDomain.
static class Program
{
private const string DELETED_FILES_SUBFOLDER = "__delete";
/// <summary>
/// The main entry point for the application.
/// </summary>
[LoaderOptimization(LoaderOptimization.MultiDomainHost)]
[STAThread]
static int Main()
{
// Check if shadow copying is already enabled
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
{
// Get the startup path.
string assemblyPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string assemblyDirectory = Path.GetDirectoryName(assemblyPath);
string assemblyFile = Path.GetFileName(assemblyPath);
// Check deleted files folders existance
string deletionDirectory = Path.Combine(assemblyDirectory, DELETED_FILES_SUBFOLDER);
if (Directory.Exists(deletionDirectory))
{
// Delete old files from this folder
foreach (var oldFile in Directory.EnumerateFiles(deletionDirectory, String.Format("{0}_*{1}", Path.GetFileNameWithoutExtension(assemblyFile), Path.GetExtension(assemblyFile))))
{
File.Delete(Path.Combine(deletionDirectory, oldFile));
}
}
else
{
Directory.CreateDirectory(deletionDirectory);
}
// Move the current assembly to the deletion folder.
string movedFileName = String.Format("{0}_{1:yyyyMMddHHmmss}{2}", Path.GetFileNameWithoutExtension(assemblyFile), DateTime.Now, Path.GetExtension(assemblyFile));
string movedFilePath = Path.Combine(assemblyDirectory, DELETED_FILES_SUBFOLDER, movedFileName);
File.Move(assemblyPath, movedFilePath);
// Copy the file back
File.Copy(movedFilePath, assemblyPath);
bool reload = true;
while (reload)
{
// Create the setup for the new domain
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = assemblyFile;
setup.ShadowCopyFiles = true.ToString().ToLowerInvariant();
// Create an application domain. Run
AppDomain domain = AppDomain.CreateDomain(setup.ApplicationName, AppDomain.CurrentDomain.Evidence, setup);
// Start application by executing the assembly.
int exitCode = domain.ExecuteAssembly(setup.ApplicationName);
reload = !(exitCode == 0);
AppDomain.Unload(domain);
}
return 2;
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
MainForm mainForm = new MainForm();
Application.Run(mainForm);
return mainForm.ExitCode;
}
}
}
As it's an interesting use case of MEF, I've bashed out a quick demo of how to hot-swap running code in C#. This is very simple and leaves out a lot of edge cases.
https://github.com/i-e-b/MefExperiments
Notable classes:
src/PluginWatcher/PluginWatcher.cs -- watches a folder for new implementations of a contract
src/HotSwap.Contracts/IHotSwap.cs -- minimal base contract for a hot-swap
src/HotSwapDemo.App/Program.cs -- Does the live code swap
This doesn't lock the task .dlls in the Plugins folder, so you can delete old versions once new ones are deployed.
Hope that helps.
You asked specifically to use ShadowCopy for the update process. If that (and why would it be?) not a fixed requirement, these ones were real eye openers for me:
https://visualstudiomagazine.com/articles/2017/12/15/replace-running-app.aspx
https://www.codeproject.com/Articles/731954/Simple-Auto-Update-Let-your-application-update-i
It comes down to you renaming the target file (which is allowed, even when it is locked since it is running) and then moving/copying the desired file to the now freed destination.
The vs-magazine article is very detailed, including some nifty tricks like finding out if a file is in use by current application (though only for exe, for .dlls and others one has to come up with a solution).

Simple ASMX WebService and DLL not loaded

I've a problem running an ASMX Web Service. I'm Calling a DLL from a method (AceptaTools.dll) and this DLL load ca4xml.dll.
AceptaTools.dll has been registered with REGSVR32. But ca4xml.dll Can't.
When i Invoke the service:
_objURL = _CA4XML.GetLastResponse();
i get a message "ca4xml.dll not loaded".
Looking al Dependency Walker:
Here both files in detail:
Both DLL are in BIN folder and my project run as x86... Why can't load?? Please help.
[WebMethod]
public string Send(string Ip, string Puerto, string NroDocumento, string TipoDocumento, string Comando, string Impresora, string Linea)
{
try
{
int _Result = 0;
string _Null = "";
string _objURL;
//Config Capsula
string serverConfig = "cfg|" + Ip.ToString() + "|" + Puerto.ToString() + "|10";
//Impresora FACTURA,1 por Defecto.
if (string.IsNullOrEmpty(Impresora)) { Impresora = "FACTURA,1"; }
if (string.IsNullOrEmpty(NroDocumento)) { NroDocumento = "0"; }
if (string.IsNullOrEmpty(Comando)) { Comando = "generar"; }
//Nuevo CAXML Cliente
AceptaTools.CA4XML_Client _CA4XML = new CA4XML_Client();
_Result = _CA4XML.Send(ref serverConfig, ref NroDocumento, ref Comando, ref Impresora, ref Linea, out _Null);
if (_Result != 0)
{
_objURL = _CA4XML.GetLastResponse(); //Get URL
return _objURL.ToString();
}
else
{
return "Error";
}
}
catch (Exception ex)
{
return ex.Message.ToString();
}
}
}
Did you make sure that the ca4xml.dll is deployed properly? Since I guess it is not referenced as .NET assembly the VS will treat it like a normal file and you will need to specifically tell VS to include it when deploying.
Do the following steps to check whether deployment has been setup properly:
Open the Solution Explorer -> Got to the ca4xml.dll -> Right click -> Select Properties -> Set Build Action = None & Copy to Output = Always
Also in addition to the Dependency Walker I suggest to use Process Monitor. When you use the file access view (disregarding registry changes etc.) you can see all the locations where a process tries to load a dll from. Afterwards you can make sure that the dll you are missing is in one of the listed locations, here is the link:
http://technet.microsoft.com/en-us/sysinternals/bb896645
When you do not load DLL in the program because you need to update some things .
you can update project in nuget manager

Application Specific Paths for DLL Loading when DLL is loaded dynamically

I am building a program that uses a very simple plugin system. This is the code I'm using to load the possible plugins:
public interface IPlugin
{
string Name { get; }
string Description { get; }
bool Execute(System.Windows.Forms.IWin32Window parent);
}
private void loadPlugins()
{
int idx = 0;
string[] pluginFolders = getPluginFolders();
Array.ForEach(pluginFolders, folder =>
{
string[] pluginFiles = getPluginFiles(folder);
Array.ForEach(pluginFiles, file =>
{
try
{
System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFile(file);
Array.ForEach(assembly.GetTypes(), type =>
{
if(type.GetInterface("PluginExecutor.IPlugin") != null)
{
IPlugin plugin = assembly.CreateInstance(type.ToString()) as IPlugin;
if(plugin != null)
lista.Add(new PluginItem(plugin.Name, plugin.Description, file, plugin));
}
});
}
catch(Exception) { }
});
});
}
When the user selects a particular plugin from the list, I launch the plugin's Execute method. So far, so good! As you can see the plugins are loaded from a folder, and within the folder are several dll's that are needed but the plugin. My problem is that I can't get the plugin to 'see' the dlls, it just searches the launching applications startup folder, but not the folder where the plugin was loaded from.
I have tried several methods:
1. Changing the Current Directory to the plugins folder.
2. Using an inter-op call to SetDllDirectory
3. Adding an entry in the registry to point to a folder where I want it to look (see code below)
None of these methods work. What am I missing? As I load the dll plugin dynamically, it does not seem to obey any of the above mentioned methods. What else can I try?
Regards,
MartinH.
//HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
Microsoft.Win32.RegistryKey appPaths = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(
string.Format(
#"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{0}",
System.IO.Path.GetFileName(Application.ExecutablePath)),
Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree);
appPaths.SetValue(string.Empty, Application.ExecutablePath);
object path = appPaths.GetValue("Path");
if(path == null)
appPaths.SetValue("Path", System.IO.Path.GetDirectoryName(pluginItem.FileName));
else
{
string strPath = string.Format("{0};{1}", path, System.IO.Path.GetDirectoryName(pluginItem.FileName));
appPaths.SetValue("Path", strPath);
}
appPaths.Flush();
Use Assembly.LoadFrom not Assembly.LoadFile
Whenever I dynamically load plugins like this, I usually create an app domain and load the assembly in the new app domain. When creating an app domain, you can specify the base directory. Dependent assemblies will be loaded from this base directory.

Categories