How to load a DLL from a shell extension? - dll

I have written a small shell extension, the stub works, but want it to read some info from an SQLite database.
SQLite needs 2 DLLs:
System.Data.SQLite.dll: managed-only core assembly
SQLite.Interop.dll: native interop assembly
In the past, I have successfully built desktop applications using these 2 DLLs, by putting them in the same folder as the .exe file.
But in the case of a shell extension, there is no such thing as a .exe file, so where should I put these DLLs?
Note: Right now I deploy the shell extension with Regasm, but I plan to deploy them with WiX in the future.

As you likely already know, assemblies are found relative to the .exe and the instructions in its .exe.config file. That means your .dll will have to take care of loading its assemblies "by hand".
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ResolveLocally);
static Assembly ResolveLocally(object sender, ResolveEventArgs args)
{
string path = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
new AssemblyName(args.Name).Name + ".dll"
);
if (!File.Exists(path))
{
return null;
}
return Assembly.LoadFrom(path);
}
This is basically the same as here: How to add folder to assembly search path at runtime in .NET?

Related

Issue with nested DLL referencing in a self-contained .Net Core web app running as a Windows service

The Error
First off, lets start with the error message:
System.BadImageFormatException: 'Could not load file or assembly 'System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Reference assemblies should not be loaded for execution. They can only be loaded in the Reflection-only loader context. (0x80131058)'
In my experience, this type of error usually occurs if the DLL is missing, or it's not in the expected format/version. However, I cannot work out what I need to do to resolve this error.
The Setup
The main project is an ASP.Net Core 3.1 application.
It is configured to be able to run as a Window service and support Web API and MVC frameworks. So my Program.cs looks something like this:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
var builder = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
builder.UseWindowsService();
return builder;
}
}
That part all works great, I can run the app as a Windows service and serve razor views and web API endpoints, etc.
However, there are more projects in this solution. I have a couple of .Net 4.6 class libraries projects that are referenced by the service app. So my solution looks something like this:
Service App (NET CORE 3.1)
Lib 1 (NET 4.6)
Lib 2 (NET 4.6)
Service App references Lib 1, and Lib 1 references Lib 2.
Any code is either of these two libraries works fine and can be used by the service app.
The problem is that Lib 2 references System.Management DLL, and this is what is causing the error. Whenever I use a function that uses that DLL, I get the error above.
The Attempts So Far
My attempts so far have including:
Setting the DLL to "copy to output"
Adding the DLL reference directly to the web service app
Manually copying the DLL to the output folder. Both debug, and publish folder (note: publish is set to "self contained" application)
The Question
Basically, how can I resolve this issue? Or at least, how can I further debug the problem to potentially find a solution.
I can't repro this at the time, but I suspect this to be a bug in the way that the self-contained publication process using dotnet publish resolves transitive dependencies.
The point is, your .NET Core 3.1 application doesn't run on .NET Framework, while the library you reference, call it "Lib 2", does.
And your Lib 2 has a reference to the NuGet package System.Management, which, when installed on a project targeting .NET Framework ... does nothing, except making your project reference the .NET Framework reference assembly for System.Management, found in C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1. At runtime, the .NET Framework will load the appropriate assembly from the GAC, but the .NET Core runtime does not do this.
Now when publishing your .NET Core app, MSBuild walks through the dependencies to be copied, and (again, I suppose) looks at the reference assembly and goes "I want that one!" and copies it to your output directory.
But you can't run reference assemblies, you can just reference them, so at runtime, your application blows up with an exception stating exactly that:
Reference assemblies should not be loaded for execution
A workaround, but definitely not a solution, is to reference the same System.Management package from your .NET Core application, where the package will actually extract a DLL containing runnable code, and that one will be copied to your output directory. But this might cause other issues, such as the DLL being overwritten during build with the reference one.
I'd suggest looking at GitHub whether this is a known issue, or that this is something caused by your project setup.

SSIS Script Task reference dll programmatically

I have built software that is capable of exporting DTSX package automatically. This package among other objects has also a ScriptTask (C#). All are compiled and run just fine.
Now the new requirement is to call a class in that ScriptTask, which exists inside an external DLL we have built, so other applications can consume the same code. We did our homework, and we included this DLL into the GAC successfully during the installation of the software.
The problem is that "using our library" is still not recognized in the script.
While searching a little bit, we figured out, that we need to reference this DLL also inside the References folder. This we can do it of course via DataTools / VisualStudio UI.
The issue is that we need to do that programmatically:
We have this piece of code that generates the Project
task.ScriptingEngine.VstaHelper.LoadNewProject(task.ProjectTemplatePath, null, "MyScriptProject");
And also, we have this piece of code that creates the MainScript
task.ScriptingEngine.VstaHelper.AddFileToProject(ScriptName + ".cs", MainScript.ToString());
I am unable to figure out how I can include the reference DLL programmatically.
Updated Answer
You can programmatically update the script task by replacing the appropriate XML node in the the DTSX file
The node path depends on where the script task has been created within the SSIS package, in my case the node path was
/DTS:Executable/DTS:Executables/DTS:Executable/DTS:ObjectData/pipeline/components/component[#refId="Package\Data Flow Task\Script Component"]/properties
The #refId you will be looking for will start with Package \ Dataflow name \ Component name
This node will have sub nodes which contains the C# scripts as well as the binary that was built off this script
The property name "SourceCode" contains the C# scipts in an array called arrayElements, the array will have three sub nodes for each file, these subnodes are called arrayElement, first value is the relative path and name, second is file encoding and third is the file content
The property name "BinaryCode" contains the .dll that was build from the scripts, it also contains an arrayElement array with two entries, first the dll name and the second the base64 encoded dll binary
To get the data to populate these items you will need to create a template of the C# build directory, apply your changes, build the code and take the resulting files and replace them on their appropriate nodes
To create the template open the script via SSIS task,
Click on the project in solution explorer to and go file save VstaProject.sln
Go to the saved folder, you get the folder off the solutions properties dialog
Copy this folder somewhere so that you can reuse it to build your custom stuff
Modify the .cs files in your template directory and add your custom reference dll's to your .csproj file
Call MSBUILD in the same directory as your .csproj to output the dll, its important that you use the VS MSBUILD, VS2017 you can find it in
C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe
Take these files and package them into a XML node in the DTSX file
Initial Answer
Microsoft provides a workaround for loading DLL's that aren't in the GAC
Load assembly that isnt in the GAC
Please see below extract from a SSIS script, I loaded the JSON dll's from the nuget install directory. This DLL is not in the GAC
static ScriptMain()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.Contains("Newtonsoft.Json"))
{
string path = #"C:\Program Files\Microsoft SDKs\Azure\.NET SDK\v2.9\bin\plugins\Diagnostics\";
return System.Reflection.Assembly.LoadFile(System.IO.Path.Combine(path, "Newtonsoft.Json.dll"));
}
return null;
}

Referencing DLL's between solutions

I've found an interesting issue with using DLL's in .NET Core (.NET Framework works fine). MVCE as follows:
Create an F# (language is irrelevant) .NET Core class library with the following single file (specific code irrelevant as well):
namespace FSharpClassLibrary
module Say =
let hello name =
printfn "Hello %s" name
Create a C# .NET Core class library in a separate solution (very important--it completely works if they are all in the same solution) with the following code:
namespace CSharpClassLibrary
{
public class HelloClass
{
public void Hello(string name)
{
FSharpClassLibrary.Say.hello(name);
}
}
}
Also, add a folder dlls, copy/paste FSharpClassLibrary.dll into it, and add it as a reference to the project (otherwise, the file above won't compile). It's actually not important to move the .dll into the project, but since I'm distributing this second project independently of the first, I need to guarantee the DLL is available.
Finally, add a console app to the same solution as CSharpClassLibrary with the following file:
using System;
using CSharpClassLibrary;
namespace ConsoleApp1
{
public class Main1
{
public static void Main()
{
new HelloClass().Hello("test");
#if DEBUG
Console.Write("Press any key");
Console.ReadKey();
#endif
}
}
}
Finally, go into NuGet and add FSharp.Core version 4.3.4 to both C# projects (just to make sure everything has access to the F# language stuff; not necessary if you create the first class library with C#). Also, I am using VS 15.6; this may or may not be relevant as well.
Set the console app as the startup project and run it. You will get the following error:
System.IO.FileNotFoundException: Could not load file or assembly 'FSharpClassLibrary, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'FSharpClassLibrary, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
at ClassLibrary1.Facade.Hello1.Hello(String name)
at ConsoleApp1.Main1.Main() in C:\Users\***\source\ConsoleApp1\ConsoleApp1\Main.cs:line 11
The most curious part about this is that if I bypass the CSharpClassLibrary library and host FSharpClassLibrary.dll and HelloClass.cs in the ConsoleApp1 project, everything works as expected. The same issue happens if I use two C# DLL's, I used F# because that's what I was working with already.
What I already tried:
Cleaning and rebuilding all projects, making sure each project was referencing the latest build.
Referencing the child (FSharpClassLibrary.dll) from ConsoleApp1 as well as CSharpClassLibrary.dll.
Using the .dll from the obj folder instead of the bin folder (shouldn't matter as far as I'm aware).
Verify that FSharpClassLibrary.dll exists in ConsoleApp1's bin folder.
Double-check that all projects compile to .NET Core, no .NET Standard projects.

How to pass configuration to AppDomain from Visual FoxPro application

I made COM visible .NET DLL in C# which refers an external DLL and the external DLL reads app.config from current AppDomain. However my DLL is called from Visual FoxPro 9 so it misses content of app.config from my project.
Does Foxpro have something like app.config in .NET? I just need pass configuration to the external library via AppDomain from FoxPro.
VFP doesn't have separate AppDomains like .Net. However, if you put your DLL in the same folder where your VFP executable is, then your AppDomain.CurrentDomain.BaseDirectory is the folder where VFP executable is (IOW it is the same as VFP's justpath(Application.ServerName)). ie:
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app.config");
Also App.config is just a plain xml file. You can read and process its contents as you wish from within VFP.
VPF creates .exe file. Just create .exe.config file with the same content as dll.config.

How to call functions of a COM DLL (In VC++) from a VC++ .EXE application?

I have a COM DLL (say, xyz.dll) coded in VC++. I want to create a calling EXE application (calling.exe) which will call the functions of the COM DLL (xyz.dll).
I followed the steps in the link http://www.codeproject.com/kb/DLL/XDllPt1.aspx.
But I am not able to connect the DLL and EXE and hence not able to call the functions of the COM DLL. I am totally new to COM and VC++ programming. Can anyone kindly help me with.
I am using Visual Studio 2005.
These are the exact steps I followed--------
STEP 1: Created a solution having the DLL project (xyz.dll) project and a caller application Project (calling.exe) of template MFC Application (Dialog based). Made this calling.exe as the startup project..
STEP 2: Went to the properties by right clicking on the calling.exe Project in solution explorer. Configuration properties --> C/C++ --> General--> Additional Include Directives and added the path to the DLL Project..
Step 3: Again Right Click on the calling.exe application Project went to Properties--> Configuration properties --> Linker --> Input --> Additional Dependencies and added the path to the .Lib file for the built DLL Project.
STEP 4: Right click on calling.exe application Project, Properties --> Common Properties --> References --> Added reference to the DLL.
STEP 5: Copied the xyz.dll file to the application project directory.
STEP 6: My DLL has many header files and its corresponding source files. So, Added all the header files present in the DLL Project to my calling.exe application program. Within the OnInitDialog() function present in one of the .CPP program of the calling.exe application, I called the functions of DLL.
Just the statements
Cx objname;
objname.func();
Here Cx is the name of the class in the DLL.
I did not do any changes with the configuration settings of the EXISTING DLL project because it is The DLL which is already prepared by an expert and I am writing just the calling applaction to call the functions present in this DLL.
THANKS IN ADVANCE.
The instructions you've followed are for calling functions in an ordinary DLL, not a COM DLL. To access a COM DLL you need to go through COM.
You don't link to the DLL's lib file or include any headers, and you don't need to move the DLL.
First, make sure the DLL is registered by running regsvr32 on it.
regsvr32 "c:\..\..\xyz.dll" ; insert the correct path
Then add an #import directive to your project's stdafx.h, containing the path to the DLL.
#import "c:\..\..\xyz.dll" // insert the correct path
Right click stdafx.cpp in the file view and choose compile.
This will generate the wrapper "smart pointer" classes you need to access your DLL.
The smart pointer classes have the same names as the interfaces in your DLL, but with "Ptr" on the end.
Look at the file with a .tlh extension and the same name as your DLL in your Debug directory. It begins with a C++ namespace declaration.
This is the namespace in which the objects you are going to create from the DLL reside.
Say the namespace is XYZ and you want to instantiate a Cx object, which exposes the Ix interface.
You would do:
try {
XYZ::IxPtr obj;
obj.CreateInstance(__uuidof(XYZ::Cx));
obj->func();
} catch (_com_error e) {
printf("Error: %S\n", e.Description());
printf("Error: %S\n", e.ErrorMessage());
}
You can then continue to use it just like an ordinary pointer.
You don't delete it when you have finished with it though, it will be destroyed automatically when it goes out of scope.