Inno Setup - External .NET DLL with dependencies - c#

I am trying to use a custom DLL in a Inno Setup script during installation. I wrote a very simple function that basically checks a connection string for a MySQL database using MySQL .NET connector (there is no MySQL client on the target server). The code of this exported function is:
public class DbChecker
{
[DllExport("CheckConnexion", CallingConvention.StdCall)]
public static int CheckConnexion([MarshalAs(UnmanagedType.LPStr)] string connexionString)
{
int success;
try
{
MySqlConnection connection = new MySqlConnection(connexionString);
connection.Open();
connection.Close();
success = 0;
}
catch (Exception)
{
success = 1;
}
return success;
}
}
The function is imported this way in Inno Setup :
[Files]
Source: "..\..\MyDll\bin\x86\Release\*"; Flags: dontcopy;
and
[Code]
function CheckConnexion(connexionString: AnsiString): Integer;
external 'CheckConnexion#files:MyDll.dll,MySql.Data.dll stdcall setuponly loadwithalteredsearchpath';`
The problem is that the setup throws an exception at runtime:
Runtime Error (at 53:207):
External exception E0434352.
I think I have to use the files prefix because the function is called in the NextButtonClick event handler, before files are copied to the {app} directory.
Both MyDll.dll and MySql.Data.dll are correctly extracted to the {tmp} directory at runtime.
I tried both with and without the loadwithalteredsearchpath flag with the same result.
What I found is that this error code is a generic .NET runtime error code.
If I remove the part using MySql.Data it works perfectly fine (except that it does nothing...)
As advised on other threads I've been trying to log the error in my .NET code using EventLog and UnhandledException but I have the same exception no matter what (and no log source is created), even without the MySQL part. I checked EventLog permissions on my computer.
It seems that the exception is thrown as soon as I use anything else that "basic" C# code (whenever I try to load another DLL).

There is probably a better way, but this will do.
Implement an initialization function (Init here) that sets up AppDomain.AssemblyResolve handler that looks for an assembly in the path of the main (executing) assembly:
[DllExport("Init", CallingConvention.StdCall)]
public static void Init()
{
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
}
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
string location = Assembly.GetExecutingAssembly().Location;
AssemblyName name = new AssemblyName(args.Name);
string path = Path.Combine(Path.GetDirectoryName(location), name.Name + ".dll");
if (File.Exists(path))
{
return Assembly.LoadFrom(path);
}
return null;
}
Import it to the Inno Setup:
procedure Init(); external 'Init#files:MyDll.dll stdcall setuponly';
And call it before calling the function that needs the dependency (CheckConnexion).
Another solution might be this:
Embedding DLLs in a compiled executable
Btw, no need for the loadwithalteredsearchpath flag. It has no effect on .NET assemblies imo. They are needed for native DLL dependencies: Loading DLL with dependencies in Inno Setup fails in uninstaller with "Cannot import DLL", but works in the installer.

I found something else that might be helpful for anyone stumbling upon this page.
In my scenario, I have several C# methods that I call from InnoSetup using DllExport. In one of those methods, I call another of the methods. This caused Inno to throw "External exception E0434352".
If I moved the code to a method not called by InnoSetup, everything worked fine.
So...
[DllExport("Fu", CallingConvention = CallingConvention.StdCall)]
public static int Fu()
{
// Stuff
}
[DllExport("Bar", CallingConvention = CallingConvention.StdCall)]
public static int Bar()
{
Fu();
}
...causes InnoSetup to cry, but:
[DllExport("Fu", CallingConvention = CallingConvention.StdCall)]
public static int Fu()
{
LocalFu();
}
private static int LocalFu()
{
// Stuff
}
[DllExport("Bar", CallingConvention = CallingConvention.StdCall)]
public static int Bar()
{
// Stuff
LocalFu();
// Other stuff
}
...is fine.
I don't know if this is caused by Inno or DllExport, so I'll forgo direct derision and blame society as a whole for my lost morning. (Or myself for being a new to this thing.)

I would like to expand upon Martin's answer. There is a way to resolve the assemblies without having to call an Init method first and that is by including a static constructor in your .NET class:
public class MyClass
{
static MyClass()
{
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += MyResolveEventHandler;
}
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
var location = Assembly.GetExecutingAssembly().Location;
var assemblyName = new AssemblyName(args.Name);
var path = Path.Combine(Path.GetDirectoryName(location), assemblyName.Name + ".dll");
if (File.Exists(path))
{
return Assembly.LoadFrom(path);
}
return null;
}
}

Related

AppDomain in Azure Function

I have tried to create an AppDomain within Azure Functions to run untrusted code. Creating the domain seems to work fine, but when I try to load in assemblies, it seems like they get loaded in incorrectly.
First I tried a simple AppDomain:
public class Sandboxer
{
public void Run()
{
AppDomain newDomain = AppDomain.CreateDomain("name");
var obj = newDomain.CreateInstance(typeof(OtherProgram).Assembly.FullName, typeof(OtherProgram).FullName).Unwrap();
}
}
public class OtherProgram : MarshalByRefObject
{
public void Main(string[] args)
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
foreach (var item in args)
Console.WriteLine(item);
}
}
I got an error
"System.IO.FileNotFoundException : Could not load file or assembly 'Sandboxer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2cd9cb1d6fdb50b4' or one of its dependencies. The system cannot find the file specified."
I then tried to set the appliactionBase to the folder with my dll in it.
public class Sandboxer
{
public void Run()
{
var location = typeof(OtherProgram).Assembly.Location;
AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationBase = Path.GetDirectoryName(location);
AppDomain newDomain = AppDomain.CreateDomain("name", null, ads);
var obj = newDomain.CreateInstance(typeof(OtherProgram).Assembly.FullName, typeof(OtherProgram).FullName).Unwrap();
var other = obj as OtherProgram;
var other2 = obj as MarshalByRefObject;
}
}
public class OtherProgram : MarshalByRefObject
{
public void Main(string[] args)
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
foreach (var item in args)
Console.WriteLine(item);
}
}
In this case, "other" is null at the end of the Run() method, but "other2" is a __TransparentProxy. It seems like it is finding and loading the dll, but doesn't understand the type.
How can I fix this problem? Thanks!
Cross posted here: https://social.msdn.microsoft.com/Forums/azure/en-US/59b119d8-1e51-4460-bf86-01b96ed55b12/how-can-i-create-an-appdomain-in-azure-functions?forum=AzureFunctions&prof=required
In this case, "other" is null at the end of the Run() method, but "other2" is a __TransparentProxy. It seems like it is finding and loading the dll, but doesn't understand the type.
According to your description, I could encounter the similar issue, I tried to create a Console application to check this issue and found that the code could work as expected under a Console application.
For Azure Function, obj as OtherProgram always returns null. Then I tried to instantiate OtherProgram under the current domain as follows:
var obj=AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap(typeof(OtherProgram).Assembly.Location, typeof(OtherProgram).FullName);
OtherProgram op = obj as OtherProgram;
if (op != null)
op.PrintDomain(log);
The above code could work as expected, but I did not found why the object under a new AppDomain always returns null. You may try to add a issue under Azure/Azure-Functions.
This is how I would do it in a conventional .NET application, should work in Azure Functions:
Register to the AppDomain.AssemblyResolve event on the newly created AppDomain
In the event handler, resolve the assembly path using the Function Directory / Function App Directory in order to point to the bin folder
AppDomains are not usable with Azure Functions. In order to properly sandbox code in Azure Functions, you would have to create a new Azure Functions App and run the code there.
If you are allowing users to write scripts, you can use another language like Lua that allows easy sandboxing.

.net load latest version of assembly

I have a .net application that references the Microsoft.SqlServer.Smo assembly.
The assembly is not distributed with the application. Instead the sql sdk is installed in the system and the dll is registered in the GAC so that the application can load.
There is no problem with this except that on some target machines I have the v12 of the SDK, while on others I have the v13 of the SDK (that usually comes installed with SSMS).
I would like the application to load the latest version of whatever is available on the system, so v13 or, if not available, v12.
Is it possible to achieve this in code or through the application config?
The short answer to the question is to set SpecificVersion to false as correctly suggested by #sevzas.
Anyway, if on the system is installed SSMS 2016 update 13.0.16000.28, the 13.100.0.0 of the dll will be registered in the GAC and with the above change, this is the version that it will be loaded. Unfortunately this version is not meant to be used by 3rd party developers but only by Microsoft products, so trying to load it will generate an exception (see here). Someone could wonder at this point why they register it in the GAC if they don't want people to use it.
Anyway, I found a way to load the v13.0 (or previous, or future 14) with the below code by using the assembly resolve event.
static int Main(string[] args)
{
//we set an event handler at the begging of our program
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
//your stuff
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
//if the dll is a sqlserver dll, we do our trick
if(args.Name.StartsWith("Microsoft.SqlServer"))
return LoadSqlAssembly(args.Name);
return null;
}
private static readonly int[] SqlVersions = new int[] {14, 13, 12, 11};
private static bool _reEntry = false;
private static Assembly LoadSqlAssembly(string name)
{
if (_reEntry)
return null;
name = name.Split(',')[0];
foreach (var version in SqlVersions)
{
try
{
_reEntry = true;
var ret = Assembly.Load($"{name}, Version={version}.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL");
//Logger.InfoFormat("Loaded {0} version {1}", name, version);
return ret;
}
catch (Exception)
{
//ignore exception
}
finally
{
_reEntry = false;
}
}
return null;
}
```

Incorrect ExecutingAssembly being used

I have a utilities library (dll) which contains the class (shown below) for working with embedded resources. In Visual Studio 2013 it works as expected and loads the resources from the assembly which calls the function in the utilities library.
In Visual Studio 2015 when I call the functions the code attempts to load the resources from the utilities library instead of the calling library. They are seperate assemblies.
Please can you help me understand why this is happening, and how I can get it to work in Visual Studio 2015?
Thanks in advance.
public static class EmbeddedResources
{
public static string[] GetAllResourceNames()
{
Assembly _assembly = Assembly.GetExecutingAssembly();
return _assembly.GetManifestResourceNames();
}
public static string ReadQueryResource(string resourceName)
{
Assembly _assembly;
StreamReader _textStreamReader;
try
{
_assembly = Assembly.GetExecutingAssembly();
_textStreamReader = new StreamReader(_assembly.GetManifestResourceStream(resourceName));
if (_textStreamReader.Peek() != -1)
return _textStreamReader.ReadToEnd();
}
catch
{
//MessageBox.Show("Error accessing resources!");
}
return null;
}
}
Assembly.GetExecutingAssembly()
Gets the assembly that contains the code that is currently executing.
Assembly.GetCallingAssembly()
Returns the Assembly of the method that invoked the currently executing method.

Change .dll in runtime

I have a huge application where one project of my solution makes reports.
I want to add new report (update report) without building my project, just add .dll files. I read about Assembly and
AppDomain, but I don't know is it really good way to add new dll for new report and how to update old report in runtime?
Here's my example, it takes my first dll, but second time it doesn't. First dll - sum, second - deducted.
static void Main(string[] args)
{
try
{
//first domain
AppDomain domain = AppDomain.CreateDomain("MyDomain");
AssemblyDll asb1 = new AssemblyDll();
Console.WriteLine(asb1.AssemblyMethod(1));
AppDomain.Unload(domain);
Console.ReadKey();
//second domain
AppDomain newDomain = AppDomain.CreateDomain("myNewDomain");
AssemblyDll asb2 = new AssemblyDll();
Console.WriteLine(asb2.AssemblyMethod(2));
AppDomain.Unload(newDomain);
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
public class AssemblyDll
{
public string AssemblyMethod(int version)
{
//loading .dll
Assembly assembly = Assembly.LoadFrom(#"../../../../Assembly/DynamicDLL" + version + ".dll");
Type type = assembly.GetType("DynamicDLL.Dynamic");
object instance = Activator.CreateInstance(type);
MethodInfo[] methods = type.GetMethods();
//invoke method
object result = methods[0].Invoke(instance, new object[] { 5, 3 });
return result.ToString();
}
}
My .dll file comes from:
namespace DynamicDLL
{
public class Dynamic
{
public int DynamicMethod(int a, int b)
{
return a + b;
//return a - b;
}
}
}
If you want to write something like plugins and like the plugin approach, you should take a look at MEF http://msdn.microsoft.com/en/library/vstudio/dd460648.aspx
MEF allows you to use any assembly dynamically and even drop dlls into a folder and build a MEF catalog out of it.
Actually Visual Studio and uses MEF internally for extensiblility (Plugins...)
Assemblies are generally loaded into an AppDomain once and you cannot unload them once loaded.
You can create a new AppDomain and load your assemblies into this and when you release this the assemblies will be unloaded. However the caveat here is you cannot directly communicate between two AppDomain you have to marshal between the two using some other method like remoting.
There's been much wrote on this in terms of plugins and making plugins unloadable, a quick Google search presented these:
http://www.brad-smith.info/blog/archives/500
http://adrianvintu.com/blogengine/post/Unloadable-plugins.aspx
Hopefully these will aid you.

C# Autonomous Update

in my new project (windows-C#-vs2008)I want the executable to be able to autonomously update itself- perhaps from a network server
Here What I do is download any updated dlls to "isolated storage" (each as separate dlls) then modify the CurrentDomain_AssemblyResolve() method
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
such that it first checks for the presence of library in its isolated storage before loading it from executable .
But what if I want to change something in the main exe.
[I tried creating a loader project(console ap) from which, it calls my main program(changed output as dll) ,however this time the assembly resolve event is not getting trigger because the reference dlls are geting generated under bin folder of new loader project during compilation ]
Any help would be highly appreciated..
public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
string assemName = new AssemblyName(args.Name).Name;
object i_StoreRootDir = i_StorageFile.GetType().GetField
("m_RootDir", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(i_StorageFile);
string i_StorePath = CombinePaths(i_StoreRootDir.ToString(), I_STOREDIR, assemName, DLL_EXT);
if (File.Exists(i_StorePath))
{
return Assembly.LoadFrom(i_StorePath);
}
else
{
//load it from resource.
return null;
}
}
catch (Exception)
{
throw;
}
You may take a look at ClickOnce deployment as part of the framework. You may also checkout wyUpdate. There's also BitsUpdater.

Categories

Resources