Using Roslyn to load several projects - c#

I am using Roslyn to display the C# projects that reference a given assembly by using the following code:
private static void ProcessProject(string projectName, string referenceName)
{
try
{
IWorkspace workspace = Workspace.LoadStandAloneProject(projectName);
ISolution solution = workspace.CurrentSolution;
string upper = referenceName.ToUpper();
foreach (IProject project in solution.Projects)
{
IEnumerable<MetadataReference> metadataReferences = project.MetadataReferences;
foreach (MetadataReference metadataReference in metadataReferences)
{
if (metadataReference.Display.ToUpper().Contains(upper))
{
Console.WriteLine(project.Name);
}
}
}
}
catch (Exception e)
{
Console.Error.WriteLine(e);
}
}
The problem is that the memory is growing up (as I have hundreds of projects to analyze) and it end up with out of memory exception.
Any idea of how to unload the workspace/solution/project to free used memory?

Since you're using IWorkspace that makes me believe that you're using a very, very old version of Roslyn. Make sure you're using the current version from NuGet, because we changed a lot since then. And fixed oh so many bugs.
In the current API, we got rid of the interface and Workspace implements IDisposable, so you can dispose it like any other disposable type by calling Dispose(). It's entirely possible the ancient version you're using also is disposable, but I honestly don't remember.

Related

Replaceable assemblies for plugin

we are trying to hot swap (update) assemblies, the normal workflow is that we make some changes, build the assembly, do some changes and build again and in an ideal world the host app would get the new version of the assembly (with the updated types).
Here's our small plugin loader class:
public class PluginLoader<T>
{
private CompositionContainer _compositionContainer;
private RegistrationBuilder _registrationBuilder;
private DirectoryCatalog _catalog;
[ImportMany(AllowRecomposition = true)]
public IList<T> Plugins { get; set; }
public PluginLoader(string pluginsDirectory)
{
Plugins = new List<T>();
SetShadowCopy();
_registrationBuilder = new RegistrationBuilder();
_registrationBuilder
.ForTypesDerivedFrom(typeof(T))
.SetCreationPolicy(CreationPolicy.NonShared)
.Export<T>();
_catalog = new DirectoryCatalog(pluginsDirectory, _registrationBuilder);
_compositionContainer = new CompositionContainer(_catalog, CompositionOptions.DisableSilentRejection);
_compositionContainer.ComposeParts(this);
}
public void Reload()
{
_catalog.Refresh();
_compositionContainer.ComposeParts(this);
}
private static void SetShadowCopy()
{
AppDomain.CurrentDomain.SetShadowCopyFiles();
AppDomain.CurrentDomain.SetCachePath(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "ShadowCopyCache"));
AppDomain.CurrentDomain.SetShadowCopyPath(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins"));
}
}
We have code to recognize a new plugin dropping into the plugins folder using FileSystemWatcher, and we call Reload when that happens, but the new versions of assemblies aren't actually loaded.
Any pointers?
Notes:
No new or deleted types are recognized, it's as if it doesn't recognize the new assembly at all.
We checked and there are no composition and other errors either, so we are a bit lost :D
It is important to note that if we build the same non recognized assembly with a different compiler(Roslyn), then it is recognized (which points to nothing being badly setup, just that the assembly needs to be somehow different )
The methods called in SetShadowCopy are deprecated. You cannot enable ShadowCopy on an existing AppDomain. For an example on how to enable ShadowCopy on a new AppDomain, Have a look at this answer.
DirectoryCatalog.Refresh does update already loaded assemblies. It only checks for file deletions and additions. Have a look at this answer for a crude work-around. Note thought that I'm not sure if such an approach is thread-safe or production-ready since I have only tested simple scenarios. Another approach would be to create your own DirectoryCatalog that can handle updates as well. The MEF source code is available (as it is for the rest of the framework). The tricky part is thread-safety since the DirectoryCatalog implementation is using Microsoft's internal classes for locking.

How can I resolve MSI paths in C#?

I need to resolve target paths from an MSI database, outside of the installation. I am currently doing this using the Wix SDK by querying the database's Directory and File tables and constructing the paths from there, but resolving paths seems like something that should already be built-in. Is there a library that does this, even something unofficial, or am I stuck with doing it on my own?
This question has already been asked for C++, but the only answer somehow misunderstood the question to be about strings.
I don't really mind performance. My real concern is with resolving special folders like ".:Fonts", ".:Windows", ".:WinRoot", etc. - which I can still do in my own code but not very elegantly.
I did the same thing you did when DTF first came out. I wrote all the queries and loops to get the data I was working for. And the performance was kind of painful.
Then I noticed the InstallPackage class in the Microsoft.Deployment.WindowsInstaller.Package assembly. I felt kind of silly when I saw how fast and simple the following code is using that class:
using System;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Deployment.WindowsInstaller.Package;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
using (var package = new InstallPackage("foo.msi", DatabaseOpenMode.ReadOnly))
{
foreach (var filePath in package.Files)
{
Console.WriteLine(filePath.Value);
}
Console.WriteLine("Finished");
Console.Read();
}
}
}
}

why reflection.assembly loadfile doesn't load all dlls?

I am using LoadFrom(), to load dlls, but for some reason this load function doesn't work on all dlls,
i want to load 3000 dlls to get from each one the copyright attribute.
my code :
class ReverseDLL
{
private Assembly assembly;
private AssemblyDescriptionAttribute desc;
private AssemblyTitleAttribute title;
private AssemblyCopyrightAttribute copyRight;
public string getCopyright(string path)
{
try
{
//assembly = System.Reflection.Assembly.Load(System.IO.File.ReadAllBytes(path));
assembly = System.Reflection.Assembly.LoadFrom(path);//"C:\\Windows\\winsxs\\x86_microsoft.vc90.debugcrt_1fc8b3b9a1e18e3b_9.0.30729.1_none_bb1f6aa1308c35eb\\msvcm90d.dll");//path);// LoadFrom(path);
desc = (AssemblyDescriptionAttribute)
AssemblyDescriptionAttribute.GetCustomAttribute(
assembly, typeof(AssemblyDescriptionAttribute));
title = (AssemblyTitleAttribute)
AssemblyTitleAttribute.GetCustomAttribute(
assembly, typeof(AssemblyTitleAttribute));
copyRight = (AssemblyCopyrightAttribute)AssemblyCopyrightAttribute.GetCustomAttribute(assembly, typeof(AssemblyCopyrightAttribute));
}
catch
{
this.copyRight = new AssemblyCopyrightAttribute("");
}
if (this.copyRight == null)
this.copyRight = new AssemblyCopyrightAttribute("");
return copyRight.Copyright;
}
}
I don't know about the reflection problem without you providing more info (such as the error), but you could also try access the file itself:
string copyright = FileVersionInfo.GetVersionInfo(path).LegalCopyright;
This accesses the file-system meta-data (like you would see in explorer), and has the advantage of working for both managed and unmanaged dlls; but it requires that meta-data to exist (it doesn't look at the attribute).
Edit: a quick check indicates that (as expected) the compiler does check for this attribute and populate the file meta-data correctly.
Have you tried stopping on exceptions? Ctrl + Alt + E, stop on framework exceptions when they are thrown. The exception message should give you some information as to why the DLL couldn't be loaded.
Using reflection is not the optimal approach, as some of the dll may have dependencies you don't have.
Using a metadata parser can give you the things you want,
http://ccimetadata.codeplex.com/
http://www.mono-project.com/Cecil
The way Marc mentioned does not work for most .NET specific metadata.

Using an embedded DLL?

Is there any detailed guide on how to use a resource embedded dll within a c# source? All the guides I find on Google don't seem to help much. It's all "make a new class" or "ILMerge" this and ".NETZ" that. But I'm not sure on how to use the ILMerge and .NETZ stuff, and the guides on classes leave out what to do after making the class file, since I find nothing new after doing so. For example, this. After adding the class and function, I have no idea on how to reach out to get the dll from my resources.
So, to be specific, what I'm looking for is a guide on how to use a .dll file that was embedded into Resources to be able to call a class, without and parts left out. Please keep in mind that I am not very experienced with C# coding. Thanks in advance. :D
PS. Try not to use those big words. I tend to get lost easily.
You can get a Stream to the DLL using Assembly.GetManifestResourceStream, but in order to do anything with it you'll either need to load it into memory and call Assembly.Load, or extract it to the file system (and then quite possibly still call Assembly.Load or Assembly.LoadFile, unless you've actually already got a dependency on it).
After loading the assembly you'd have to use reflection to create instances of classes or call methods etc. All of this is quite fiddly - and in particular I can never remember the situations in which to call the various overloads of Assembly.Load (or similar methods). Jeff Richter's "CLR via C#" book would be a useful resource to have at your desk.
Could you give more information about why you need to do this? I've used manifest resources for various things, but never to include code... is there any reason you can't ship it alongside your executable?
Here's a complete example, albeit without error checking:
// DemoLib.cs - we'll build this into a DLL and embed it
using System;
namespace DemoLib
{
public class Demo
{
private readonly string name;
public Demo(string name)
{
this.name = name;
}
public void SayHello()
{
Console.WriteLine("Hello, my name is {0}", name);
}
}
}
// DemoExe.cs - we'll build this as the executable
using System;
using System.Reflection;
using System.IO;
public class DemoExe
{
static void Main()
{
byte[] data;
using (Stream stream = typeof(DemoExe).Assembly
.GetManifestResourceStream("DemoLib.dll"))
{
data = ReadFully(stream);
}
// Load the assembly
Assembly asm = Assembly.Load(data);
// Find the type within the assembly
Type type = asm.GetType("DemoLib.Demo");
// Find and invoke the relevant constructor
ConstructorInfo ctor = type.GetConstructor(new Type[]{typeof(string)});
object instance = ctor.Invoke(new object[] { "Jon" });
// Find and invoke the relevant method
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(instance, null);
}
static byte[] ReadFully(Stream stream)
{
byte[] buffer = new byte[8192];
using (MemoryStream ms = new MemoryStream())
{
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, bytesRead);
}
return ms.ToArray();
}
}
}
Building the code:
> csc /target:library DemoLib.cs
> csc DemoExe.cs /resource:DemoLib.dll
You might need to use ILMerge. Looks like it will solve the problem naturally for you.
http://research.microsoft.com/en-us/people/mbarnett/ILMerge.aspx

How do you call a managed (C#) function from C++?

I have a C# DLL file project (my_cs_dll.dll) which defines a static class with a static member function.
namespace Foo
{
public static class Bar
{
public static double GetNumber() { return 1.0; }
}
}
I also have a C++ DLL project which is using /clr.
#using <my_cs_dll.dll>
double get_number_from_cs() { return Foo::Bar::GetNumber(); }
I've added a reference to 'my_cs_dll.dll' in the C++ project Common Properties references section (copy local/copy dependencies are both True).
And I've also added the path to 'my_cs_dll.dll' in the C++ project Configuration Properties C/C++ General 'Resolve#using References' section.
Everything builds without error, however at runtime I keep getting a 'System.IO.FileNotFound' exception from the system claiming it can't find the my_cs_dll.dll assembly.
Both DLL files are definitely present in the same directory from which I'm running.
I have tried all sorts of variations on the settings mentioned above and read everything I could find on manged/unmanaged interop, but I can't seem to get my brain around what is wrong...
I'm using Visual Studio 2008 and .NET 3.5.
It sounds like your C# assembly is not being resolved at runtime. Is your C# dll in the same directory as (or a subdirectory of) your executable? It's been a while since I did this, but my recollection is that unless your assembly is installed in the GAC, it must be in the directory (or a subdirectory) where your executable is located, as opposed to the location of the dll that's using it. This has to do with the .NET security features.
If you are still having problems, you can try using resolving the assembly yourself. In your clr-enabled C++ project, try adding the following:
using namespace System;
using namespace System.Reflection;
void Resolve()
{
AppDomain::CurrentDomain->AssemblyResolve +=
gcnew ResolveEventHandler(OnAssemblyResolve);
}
Assembly ^OnAssemblyResolve(Object ^obj, ResolveEventArgs ^args)
{
#ifdef _DEBUG
String ^path = gcnew String(_T("<path to your debug directory>"));
#else
String ^path = gcnew String(_T("<path to your release directory>"));
#endif
array<String^>^ assemblies =
System::IO::Directory::GetFiles(path, _T("*.dll"));
for (long ii = 0; ii < assemblies->Length; ii++) {
AssemblyName ^name = AssemblyName::GetAssemblyName(assemblies[ii]);
if (AssemblyName::ReferenceMatchesDefinition(gcnew AssemblyName(args->Name), name)) {
return Assembly::Load(name);
}
}
return nullptr;
}
You may have to tweak the code a little bit to get it to compile in your project. In my case, I made the two functions static methods of a class in my clr-enabled project. Just make sure you call the Resolve() function early on in your code, i.e., before you try to call get_number_from_cs().
While using COM is an option, it is not necessary. You're on the right path with your current approach. If you want some hand-holding, take a look at this CodeProject example. It's the one I following to get my unmanaged application to use my managed assemblies.

Categories

Resources