I have a main application which loads drivers from their compiled dll files. I am running into a problem where I want to be able to debug these dll files but the symbols are not being loaded by the project.
The dll files are part of the solution but have to be built separately from the main application. The main application then at runtime loads these dlls from a directory. I am having an error in one of these loaded dll files (not an exception, just unexpected results) but cannot debug the files. Can anyone give me an idea on how to proceed with debugging these?
EDIT:
To give a better idea of how the dlls are loaded here is the code from the main project:
public List<BaseClass> getObjects(string objectName)
{
List<BaseClass> availableDrivers = new List<BaseClass>();
string currentDir = Directory.GetCurrentDirectory();
currentDir = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); //Use this to find path even when running as service
if (Directory.Exists(currentDir + "\\Objects"))
{
string[] files = Directory.GetFiles(currentDir + "\\Objects", "*.dll");
foreach (string file in files)
{
Console.WriteLine("LOOKING AT:"+ file);
try
{
Assembly dll = Assembly.LoadFrom(file);
Type[] types = dll.GetTypes(); // .Where(x => x.BaseType.Name == "BaseClass");
foreach (Type type in types)
{
if (type.BaseType != null && (type.BaseType.Name == "BaseClass" || type.BaseType.Name == "PeriodicBaseClass"))
{
BaseClass ClassObj = (BaseClass)Activator.CreateInstance(type);
if (objectName == "" || objectsName == ClassObj.Name)
{
availableDrivers.Add(ClassObj);
}
}
}
}
catch (Exception e)
{
Error.Log("Error reading driver:" + e.Message,MessageType.Error);
//Console.WriteLine("Error reading driver:" + e.Message);
}
}
}
return availableDrivers;
}
So as you can see, when I run the program itself the drivers get loaded by retrieving their compiled dll files and when putting breakpoints in the dll code I get the message saying symbols cannot be loaded. I have tried checking Debug>Windows>Modules but the dlls don't show up there due to not being loaded directly in the application.
If the dlls in question are debug versions (that is, they were compiled for debugging) and their current .pdb files are in the same directory, you should be able to step through them just as if they were in your own project.
The other alternative is to open the solution that builds these dll and debug it by attaching to the process of the program you are running.
https://msdn.microsoft.com/en-us/library/3s68z0b3.aspx
So I ended up solving this problem. The way I did this was create a small console application in the solution that runs the same methods as the main application but right from the projects in the solution instead of loading the dlls dynamically.
I then set the startup project to the console application and was able to step through the relevant files properly.
Related
I have a plugin architecutre which is using a Shared DLL to implement a plugin interface and a base plugin class (eg. IPlugin & BasePlugin). The shared DLL is used by both the Plugin and the main application.
For some reason, when I try to cast an object that was instantiated by the main application to the shared plugin interface (IPlugin), I get an InvalidCastException saying I cannot cast this interface.
This is despite:
The class definitely implementing the plugin interface.
The Visual Studio debugger saying that "(objInstance is IPlugin)" is true when I mouse-over the statement, despite the same 'if' condition evaluating as false when I step-through the code or run the .exe.
The Visual Studio Immediate Window also confirms that the above condition is true and that is it possible to cast the object successfully.
I have cleaned/deleted all bin folders and also tried both VS2019 and VS2022 with exactly the same outcome.
I am going a little crazy here because I assume it is something to do with perhaps with multiple references of the same DLL somehow causing the issue (like this issue). The fact that the debugger tells me everything is okay makes it hard to trouble-shoot. I'm not sure if it's relevant but I have provided example code and the file structure below:
Shared.dll code
public interface IPlugin
{
}
public class BasePlugin : IPlugin
{
}
Plugin.dll code
class MyPlugin : BasePlugin
{
void Init()
{
// The plugin contains references to the main app dlls as it requires
// to call various functions inside the main app such as adding menu items etc
}
}
Main.exe
(Note: This is pseudo-code only and does not show the plugin framework used to load the plugin DLLs via Assembly.Load())
var obj = Activator.CreateInstance(pluginType); // Plugin type will be 'MyPlugin'
if (obj is IPlugin) // <== This executes as false when executed but evaluates to true in the Visual Studio debugger
{
}
var castObj = (IPlugin)obj // <== This will cause an invalid cast exception
Folder structure
|--- MainApp.exe
|--- Shared.dll
|--- Addins
|------- Plugin.dll
|------- Shared.dll
Does anyone know the reasons how I can trouble-shoot this issue and what might be causing it?
My suggestion is that (using the Properties\Signing tab) you sign your shared dll assembly with a Strong Name Key. If your plugin classes reference a signed copy of the dll, there should be no possibility of confusion.
The application has no prior knowledge of what the plugins are going to be, but it does know that it's going to support IPlugin. Therefore, I would reference the PlugInSDK project directly in the app project.
Testbench
Here's what works for me and maybe by comparing notes we can get to the bottom of it.
static void Main(string[] args)
{
Confirm that the shared dll (known here as PlugInSDK.dll) has already been loaded and display the source location. In the Console output, note the that PublicKeyToken is not null for the SDK assembly (this is due to the snk signing).
var sdk =
AppDomain.CurrentDomain.GetAssemblies()
.Single(asm=>(Path.GetFileName(asm.Location) == "PlugInSDK.dll"));
Console.WriteLine(
$"'{sdk.FullName}'\nAlready loaded from:\n{sdk.Location}\n");
The AssemblyLoad event provides the means to examine on-demand loads as they occur:
AppDomain.CurrentDomain.AssemblyLoad += (sender, e) =>
{
var name = e.LoadedAssembly.FullName;
if (name.Split(",").First().Contains("PlugIn"))
{
Console.WriteLine(
$"{name}\nLoaded on-demand from:\n{e.LoadedAssembly.Location}\n");
}
};
It doesn't matter where the plugins are located, but when you go to discover them I suggest SearchOption.AllDirectories because they often end up in subs like netcoreapp3.1.
var pluginPath =
Path.Combine(
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"..",
"..",
"..",
"PlugIns"
);
Chances are good that a copy of PlugInSDK.dll is going to sneak into this directory. Make sure that the discovery process doesn't accidentally pick this up. Any other Type that implements IPlugin gets instantiated and put in the list.
List<IPlugin> plugins = new List<IPlugin>();
foreach (
var plugin in
Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories))
{
// Exclude copy of the SDK that gets put here when plugins are built.
if(Path.GetFileName(plugin) == "PlugInSDK.dll") continue;
// Be sure to use 'LoadFrom' not 'Load'
var asm = Assembly.LoadFrom(plugin);
// Check to make sure that any given *.dll
// implements IPlugin before adding to list.
Type plugInType =
asm.ExportedTypes
.Where(type =>
type.GetTypeInfo().ImplementedInterfaces
.Any(intfc => intfc.Name == "IPlugin")
).SingleOrDefault();
if ((plugInType != null) && plugInType.IsClass)
{
plugins.Add((IPlugin)Activator.CreateInstance(plugInType));
}
}
Now just display the result of the plugin discovery.
Console.WriteLine("LIST OF PLUGINS");
Console.WriteLine(
string.Join(
Environment.NewLine,
plugins.Select(plugin => plugin.Name)));
Console.ReadKey();
}
Is this something you're able to repro on your side?
I have a small console application which backs up and then copies new DLLs to an installation folder, and then is supposed to re-register the new DLLs. It can also restore DLLs from a backup and then re-register the backups.
I have the following code to perform the registeration:
Assembly asm = Assembly.LoadFrom(Path.Combine(Path.GetFullPath(options.RemotePath), Path.GetFileName(file)));
RegistrationServices regAsm = new RegistrationServices();
bool bResult = regAsm.RegisterAssembly(asm, AssemblyRegistrationFlags.SetCodeBase);
I'm having a problem with registering where on calling Assembly.LoadFrom I get a System.IO.FileLoadException with the message A procedure imported by 'MyLib.dll' could not be loaded
I thought this was probably a DLL in the remote folder that it was unable to load, so I wrapped my registration code in a change of current directory:
var wd = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(options.RemotePath);
...
Directory.SetCurrentDirectory(wd);
Unfortunately this didn't seem to resolve the issue. I did some more investigating and found that I can tell the AppDomain how to resolve dependencies using AppDomain.CurrentDomain.AssemblyResolve and ResolveEventHandler, and so I set this up by adding this code at the beginning of Main, before my registration code.
var otherCompanyDlls = new DirectoryInfo(options.RemotePath).GetFiles("*.dll");
Console.WriteLine("Setting AssemblyResolve");
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler((sender, a) =>
{
Console.WriteLine("Looking for {0}", a.Name);
var dll = otherCompanyDlls.FirstOrDefault(fi => fi.Name == a.Name);
if (dll == null)
{
return null;
}
return Assembly.LoadFrom(dll.FullName);
});
I still get the same error, and this AssemblyResolve handler code never seems to be triggered since the Console line I added is never written.
What is it that I am doing wrong? I was hoping to at least find out what DLL dependency it was trying to load but I can't even seem to find that.
I'm working on a sort of plugin system for a Discord bot. Right now i'm on macOS using Mono as my runtime and I've been looking at using seperate AppDomains to load and manage each of the plugins. The plugins themselves do have dependencies but these are already loaded in the main appdomain. So here lies my problem:
I've attempted to iterate over the loaded assemblies in the current domain and load them into my separate AppDomain so the plugin's dependencies are already loaded in. This results in a FileNotFoundException when trying to load said dependencies (even though I point them to the correct path in the exe directory).
I've tried without doing that and just going through and loading the plugin dll's into a separate AppDomain and seeing if it'll resolve automatically. Still throws a FileNotFoundException.
I tried manually creating an array in order of the dependendies (in my application's case, Newtonsoft.Json -> DSharpPlus -> Bot.CommandManager). I can't even load Newtonsoft.Json due to a missing dependency.
Now, I'm loading the raw bytes into a stream reader and passing that into AppDomain.Load to make sure that the FileNotFoundException comes from a lack of dependency and not the actual file being missing. The file is there, still throws FileNotFoundException on domain.Load
The only way I've been able to successfully load the plugins is just using Assembly.Load and not worrying about AppDomains which works great until I update something and want to unload/reload the plugin. Which is where I need to use AppDomain. I'm kind of lost, here's my current iteration of the code. It's probably extremely unnecessary to have a separate AppDomain for each plugin but I'm rolling with that for now.
private void SetupAppDomain()
{
string path = Directory.GetCurrentDirectory() + "/modules/";
Console.WriteLine(path);
List<string> installedModules = new List<string>();
string[] files = Directory.GetFiles(path);
foreach(string modulePath in files)
{
try
{
AppDomain domain = AppDomain.CreateDomain(Path.GetFileName(modulePath), AppDomain.CurrentDomain.Evidence);
StreamReader reader = new StreamReader(modulePath, System.Text.Encoding.GetEncoding(1252), false);
byte[] b = new byte[reader.BaseStream.Length];
reader.BaseStream.Read(b, 0, System.Convert.ToInt32(reader.BaseStream.Length));
domain.Load(b); //this throws the FileNotFoundException
Assembly[] a = domain.GetAssemblies();
int index = 0;
for (int i = 0; i < a.Length; i++)
{
if(a[i].GetName().Name + ".dll" == Path.GetFileName(modulePath))
{
index = i;
break;
}
}
installedModules.Add(a[index].GetName().Name);
}
catch(Exception ex)
{
throw ex;
}
}
}
Worst case scenario, I can just go back to the old way of doing it and just have to restart the bot when I want to reload assemblies.
I am currently trying to write a small service, which uses CefSharp (v57.0.0) to render HTML to a PDF file and followed the instructions to use "Any CPU" in my project (Feature Request - Add AnyCPU Support).
In my project I used the following assembly resolver that seems to work fine (it loads CefSharp.Core.dll, CefSharp.dll during initialisation):
// Will attempt to load missing assembly from either x86 or x64 subdir
private static Assembly Resolver(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith("CefSharp", StringComparison.Ordinal))
{
string assemblyName = args.Name.Split(new[] { ',' }, 2)[0] + ".dll";
string archSpecificPath = Path.Combine(
AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
Environment.Is64BitProcess ? "x64" : "x86",
assemblyName);
var outputAssembly = File.Exists(archSpecificPath) ? Assembly.LoadFile(archSpecificPath) : null;
return outputAssembly;
}
return null;
}
For the initialisation of CefSharp I set exactly the same values like in the example:
var settings = new CefSettings()
{
// By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache")
};
// Perform dependency check to make sure all relevant resources are in our output directory.
Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);
However, if I start my simple test, I get the following error code:
Message: System.Exception : Unable to locate required Cef/CefSharp dependencies:
Missing:CefSharp.BrowserSubprocess.exe
Missing:CefSharp.BrowserSubprocess.Core.dll
Missing:CefSharp.Core.dll
Missing:CefSharp.dll
Missing:icudtl.dat
Missing:libcef.dll
Executing Assembly Path:D:\projects\CefService\bin\Debug\x86
Any ideas what might be happening here and how to solve the problem?
The message is pretty clear, other assemblies could not be loaded.
Here are some generic instructions on how to do that:
load native ones (e.g. libcef.dll) first with LoadLibrary and FreeLibrary
see if loading a managed one will automatically load other managed ones it depends, else handle them (tedious)
You might be interested in these tools for spotting dependencies:
http://www.dependencywalker.com/
https://github.com/isindicic/DependencyWalker.Net
How do I determine the dependencies of a .NET application?
I have an executable, say abc.exe, which references a.dll (same folder as executable), b.dll (in random folder), and c.dll (in random folder).
However, these DLLs are not necessarily in the same directory as the executable (or in the GAC), and that's something I cannot do anything about.
I've tried to use System.Reflection.Assembly to try to find all the referenced assemblies used by abc.exe.
foreach (AssemblyName an in assembly.GetReferencedAssemblies())
{
Assembly.Load(an);
}
This seems to load the assemblies that are in the executable's directory (a.dll), but not surprisingly throws file not found exceptions for the others.
My idea is to load the process using System.Diagnostics.Process and then reflect on the process, since that should tell me where to find b.dll and c.dll(?)
However, I don't know how to go about doing so. Is this possible, and if so, how can it be done? Thanks!
So it turns out this can be done with System.Diagnostic.Process, e.g.:
public void findModules(string executablePath)
{
Process process = new Process();
process.StartInfo.FileName = executablePath;
process.Start();
process.WaitForInputIdle();
System.Threading.Thread.Sleep(10000);
ProcessModuleCollection mods = process.Modules;
foreach (ProcessModule m in mods)
{
Console.WriteLine(m.ModuleName + ":" + m.FileName);
}
}
Where ModuleName gives you the name of the assembly and FileName gives you the correct assembly path, regardless of where it is (On the network, in C:\Windows, etc.)
However, I cannot figure out how to 'detect' when an executable has finished loading all the modules. If I remove the sleep line, I see only a partial list of modules, which I guess are the ones that are done loading by the time process.Modules is called.