I have been given a library written in C# and I need to use it in a C++ project. The C# library has been exported to a .tlb type library, which I can successfully import into my C++ project by using the #import directive.
Being utterly unfamiliar with COM I can't for the life of me figure out how to get at static member functions on any classes. Here's how I access it in C#:
void Function()
{
StaticClass.StaticMethod();
}
And then you get into the C++ side, what gets generated in the .tlh file is:
struct __declspec(uuid("some big long thing"))
/* dual interface */_StaticClass;
//long while later
_COM_SMARTPTR_TYPEDEF(_StaticClass, __uuidof(_StaticClass));
So I'm trying to figure out how to get use of the static class and haven't had any luck with Google. The only example anywhere else in any other project I have access to gives me something similar to this:
_StaticClassPtr s = _StaticClassPtr(__uuidof(_StaticClass));
but the example I have isn't for a static class anyway.
Basically I'm stuck with nowhere to even really start. This fails with "Unhandled exception at in <executable>: Microsoft C++ exception: _com_error at memory location <location>"
Edit: Since #dxiv informed me static methods aren't usable with COM interop, there's another option marked 'obsolete' that does not use static members -- problem is I get exactly the same exception when I construct the instance with similar syntax:
IInstanceClassPtr p = _IInstanceClassPtr(__uuidof(_InstanceClass));
The same exception is thrown, "_com_error at memory location"
Reading your question you would like to use C# using COM Interop. What I done is something like this. Starting from the assumption that COM is the acronym of Component Object Model and to use it you need an instantiable, local or remote, object. The example below creates an "in-proc" instance of the CLR object.
Create an interface which is exposed to COM:
namespace MyNamespace
{
/// <summary>
/// Provides an entry point for COM clients.
/// </summary>
[ComVisible(true)]
[Guid("A9E6D7FE-34FD-4A6B-9EB2-DC91F4AE567B")]
public interface IMyAccessor
{
void ExecuteStaticMethod();
// add anything else like methods, property,
}
}
then implement the interface in a C# class:
namespace MyNamespace
{
/// <summary>
/// The implementation of IMyAccessor.
/// </summary>
[ComVisible(true)]
[Guid("C65A7F81-641C-4F17-B34A-DEB88B4158E8")]
[ProgId("MyCompany.MyAccessor")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IMyAccessor))]
public sealed class MyAccessor: IMyAccessor
{
public void ExecuteStaticMethod() { StaticClass.StaticMethod(); }
}
}
export the TLB and import it in C++ project (MyAccessor is only a name I used here) in the header file of your C++ class using the following clause:
#import "MyAccessor.tlb"
Within the class header add a line like the following:
MyNamespace::IMyAccessorPtr m_IMyAccessor;
And in the class implementation use the following:
HRESULT hr = m_IMyAccessor.CreateInstance(__uuidof(MyNamespace::MyAccessor));
if (FAILED(hr))
{
// do something if failed
}
m_IMyAccessor->ExecuteStaticMethod(); // this will execute your static method in C#
NOTE: when exporting the TLB use the correct switchs. In an x64 environment (/win64) must be used to have the right pointer size: normally tlbexp returns pointer usable in a 32bit environment. This is important if you want to extend the class with more sofistcated methods.
NOTE 2: if the returned HRESULT from CreateInstance is something like "class not registered", remember to execute the registration of the TLB wih REGTLIB.
I wanted to convert a bitmap to Leptonica.Pix.. So after I did a search I found someone who had the same problem here:
Tesseract .NET Process image from memory object
So the solution to this problem was to use PixConverter.ToPix() method.
My problem here is that I can't find this method in the latest installed Leptonica Package. I tried to remove the and reinstall the lateset version thought Nuget but the method is still not not there.
What should I do to be able to use PixConverter.ToPix()?. Thanks in advance.
EDIT: I forgot to mention that i'm using the latest Tessercat pacakge too.
You need to use the version "3.0.2" for this (PixConverter.ToPix()) to work.
So your .csproj file should have this exact match in version:
<PackageReference Include="Tesseract" Version="3.0.2" />
Hope it helps.
In Tesseract 4 there is a new way to convert this using the following syntax:
var pixFromByteArray = Pix.LoadFromMemory(byteArray);
var pixFromFile = Pix.LoadFromFile(fileName);
It lives in Tesseract namespace, more information can be found here https://github.com/charlesw/tesseract
namespace Tesseract
{
/// <summary>
/// Handles converting between different image formats supported by DotNet.
/// </summary>
public static class PixConverter
{
private static readonly BitmapToPixConverter bitmapConverter = new BitmapToPixConverter();
private static readonly PixToBitmapConverter pixConverter = new PixToBitmapConverter();
/// <summary>
/// Converts the specified <paramref name="pix"/> to a Bitmap.
/// </summary>
/// <param name="pix">The source image to be converted.</param>
/// <returns>The converted pix as a <see cref="Bitmap"/>.</returns>
public static Bitmap ToBitmap(Pix pix)
{
return pixConverter.Convert(pix);
}
/// <summary>
/// Converts the specified <paramref name="img"/> to a Pix.
/// </summary>
/// <param name="img">The source image to be converted.</param>
/// <returns>The converted bitmap image as a <see cref="Pix"/>.</returns>
public static Pix ToPix(Bitmap img)
{
return bitmapConverter.Convert(img);
}
}
}
As per the sites landing page
Add the Tesseract NuGet Package by running Install-Package Tesseract from the Package Manager Console.
Also, its worth while reading the site thoroughly.
Disclaimer, i have never used this library before, just looked up the information
Update
Just to make sure i wasn't giving you bad information, I created a new project, downloaded the latest Tesseract nuget. And was able to do the following.
using Tesseract;
...
PixConverter.ToPix()
Update2
The problem you are noticing is because you are using
https://www.nuget.org/packages/tesseract.net/
apposed to
https://www.nuget.org/packages/Tesseract/
Now 'im not sure what one you actually want. However that method dosnt not exist in the former
I've encountered the need to implement a plugin pattern that doesn't fit with anything I've seen elsewhere and I'm just wondering whether I'm looking at it the wrong way or whether anyone else has encountered the same problem and might have a soluton.
Essentially, we have a system that comprises of a core assembly, and a number of modules that plug into it. Some of the modules rely on other modules, but the need has arisen to potentially remove or replace some of those dependencies from time to time and I'd like to avoid recompiles as far as possible.
The system is a bespoke CMS and the modules are plugins providing features within the CMS. For example, we have a comments module and several content modules such as a news module, a blogs module etc. that can include commenting functionality. My problem is that some customers may not purchase the comments module, so I either need to find a way to prevent the dependent modules from depending on the existence of a comments module and, in some cases, may need to cater for a modified version of the comment module.
We're loading the modules at runtime and, at present, to avoid interdependencies between the modules, we're handling this using interfaces that are held in the core CMS assembly. My concern is that to avoid having to modify the core CMS assembly every time we create new modules where a dependency could exist, I need to use something a lot looser than interfaces and implementations of those interfaces.
I'm considering the following:
Core assembly contains an object that allows the registration and unregistration of shared input/output messages (for example "Comments.AddComment" or "Comments.ListComments")
When modules are loaded, they advertise the services they require and the services
they provide (for example, a news module would require the "Comments.AddComment" message and any variant of the comments module would provide the "Comments.AddComment" message).
Any objects or data that are passed to these messages will inherit from a very loose base class or implement an interface that exposes a property of type IDictionary that is contained within the core assembly. Alternatively, the contract for a message will require only a parameter of type object and I pass anonymous objects into them from the provider/consumer.
The downside is obviously losing strong typing, but the plus is that I don't rely on a strict interface implementation or require the inclusion of modules that may not exist at runtime.
Plugins are loaded via Reflection, checking referenced assemblies and looking for classes implementing a given interface. MEF and dynamic types aren't an option as I'm restricted to .NET 3.5.
Can anyone suggest anything better, or perhaps a different way of thinking about this problem?
You're right that if you use a base class or interface in your core app, then you need to rebuild the app and all the plugins that use that class/interface if it changes. So what can you do about it? Here are some ideas (not necessarily good ones, but they may spark some thoughts) that you can mix & match...
Put the interfaces in separate shared assemblies, so you at least don't need to recompile the core app if an interface changes.
Don't change any of your interfaces - keep them fixed in stone. Instead "version" them, so if you want to change the interface, you leave the old interface in place and just expose a completely new interface that extends or replaces the old API. This allows you to gradually deprecate old plugins rather than forcing an immediate global rebuild being required. This does tie your hands somewhat as it requires full backwards compatibility support for all the old interfaces at least until you know all your clients have moved on to newer builds of all their assemblies. But you can combine this with a less frequent "reinstall everything" release where you break backwards compatibility, clear out the defunct interfaces and upgrade all the client assemblies.
Look for interfaces where some parts of the interface are not needed by all plugins, and break up some interfaces into several simpler interfaces, to reduce dependencies/churn on each interface.
As you've suggested, convert interfaces into a runtime registration/discovery approach to minimise the churn on the interfaces. The more flexible and generic your interfaces are, the easier it will be to extend them without introducing breaking changes. For example, serialize data/commands to a string format, dictionary or XML and pass it in that form, rather than calling explicit interfaces. A data-driven approach like XML or a dictionary of name+value pairs is much easier to extend than an interface, so you can start supporting new elements/attributes while easily retaining backwards compatibility for clients that pass an older format to you. Instead of PostMessage(msg) + PostComment(msg) you could genericise the interface to a single method taking a type parameter: PostData("Message", msg) and PostData("Comment", msg) - that way it's easy to support new types without needing to define new interfaces.
If possible, try to define interfaces that anticipate expected future features. So if you think you might one day add an RSS capability, then think about how that might work, chuck in an interface, but don't provide any support for it. Then if you finally get around to adding an RSS plugin, it already has a defined API to plug into. Of course, this only works if you define flexible enough interfaces that they are actually usable by the system when it is implemented!
Or in some cases maybe you can ship the dependency plugins to all your customers, and use a licensing system to enable or disable their capabilities. Then your plugins can have dependencies on each other, but your customers can't utilise the facilities unless they've bought them.
Ok, did some digging and found what I was looking for.
NOTE : this is old code, it's not using any patterns or anything like that. Heck it's not even in it's own object, but it works :-) you'll need to adapt the idea's to work the way you want.
First things first, is a loop that gets all the DLL files found in a specific directory, in my case this was in a folder called 'plugins' under the apps install folder.
private void findPlugins(String path)
{
// Loop over a list of DLL's in the plugin dll path defined previously.
foreach (String fileName in Directory.GetFiles(path, "*.dll"))
{
if (!loadPlugin(fileName))
{
writeToLogFile("Failed to Add driver plugin (" + fileName + ")");
}
else
{
writeToLogFile("Added driver plugin (" + fileName + ")");
}
}// End DLL file loop
}// End find plugins
As you will see there is a call to 'loadPlugin' this is the actual routine that does the work of recognizing and loading an individual dll as a plugin for the system.
private Boolean loadPlugin(String pluginFile)
{
// Default to a successfull result, this will be changed if needed
Boolean result = true;
Boolean interfaceFound = false;
// Default plugin type is unknown
pluginType plType = pluginType.unknown;
// Check the file still exists
if (!File.Exists(pluginFile))
{
result = false;
return result;
}
// Standard try/catch block
try
{
// Attempt to load the assembly using .NET reflection
Assembly asm = Assembly.LoadFile(pluginFile);
// loop over a list of types found in the assembly
foreach (Type asmType in asm.GetTypes())
{
// If it's a standard abstract, IE Just the interface but no code, ignore it
// and continue onto the next iteration of the loop
if (asmType.IsAbstract) continue;
// Check if the found interface is of the same type as our plugin interface specification
if (asmType.GetInterface("IPluginInterface") != null)
{
// Set our result to true
result = true;
// If we've found our plugin interface, cast the type to our plugin interface and
// attempt to activate an instance of it.
IPluginInterface plugin = (IPluginInterface)Activator.CreateInstance(asmType);
// If we managed to create an instance, then attempt to get the plugin type
if (plugin != null)
{
// Get a list of custom attributes from the assembly
object[] attributes = asmType.GetCustomAttributes(typeof(pluginTypeAttribute), true);
// If custom attributes are found....
if (attributes.Length > 0)
{
// Loop over them until we cast one to our plug in type
foreach (pluginTypeAttribute pta in attributes)
plType = pta.type;
}// End if attributes present
// Finally add our new plugin to the list of plugins avvailable for use
pluginList.Add(new pluginListItem() { thePlugin = plugin, theType = plType });
plugin.startup(this);
result = true;
interfaceFound = true;
}// End if plugin != null
else
{
// If plugin could not be activated, set result to false.
result = false;
}
}// End if interface type not plugin
else
{
// If type is not our plugin interface, set the result to false.
result = false;
}
}// End for each type in assembly
}
catch (Exception ex)
{
// Take no action if loading the plugin causes a fault, we simply
// just don't load it.
writeToLogFile("Exception occured while loading plugin DLL " + ex.Message);
result = false;
}
if (interfaceFound)
result = true;
return result;
}// End loadDriverPlugin
As you'll see above, there is a struct that holds the info for a plugin entry, this is defined as:
public struct pluginListItem
{
/// <summary>
/// Interface pointer to the loaded plugin, use this to gain access to the plugins
/// methods and properties.
/// </summary>
public IPluginInterface thePlugin;
/// <summary>
/// pluginType value from the valid enumerated values of plugin types defined in
/// the plugin interface specification, use this to determine the type of hardware
/// this plugin driver represents.
/// </summary>
public pluginType theType;
}
and the variables that tie the loader to said struct:
// String holding path to examine to load hardware plugins from
String hardwarePluginsPath = "";
// Generic list holding details of any hardware driver plugins found by the service.
List<pluginListItem> pluginList = new List<pluginListItem>();
The actual plugin DLL's are defined using an Interface 'IPlugininterface' and also an Enumeration to define the plugin types:
public enum pluginType
{
/// <summary>
/// Plugin is an unknown type (Default), plugins set to this will NOT be loaded
/// </summary>
unknown = -1,
/// <summary>
/// Plugin is a printer driver
/// </summary>
printer,
/// <summary>
/// Plugin is a scanner driver
/// </summary>
scanner,
/// <summary>
/// Plugin is a digital camera driver
/// </summary>
digitalCamera,
}
and
[AttributeUsage(AttributeTargets.Class)]
public sealed class pluginTypeAttribute : Attribute
{
private pluginType _type;
/// <summary>
/// Initializes a new instance of the attribute.
/// </summary>
/// <param name="T">Value from the plugin types enumeration.</param>
public pluginTypeAttribute(pluginType T) { _type = T; }
/// <summary>
/// Publicly accessible read only property field to get the value of the type.
/// </summary>
/// <value>The plugin type assigned to the attribute.</value>
public pluginType type { get { return _type; } }
}
for the custom attribute that we search for in a plugin to know it's ours
public interface IPluginInterface
{
/// <summary>
/// Defines the name for the plugin to use.
/// </summary>
/// <value>The name.</value>
String name { get; }
/// <summary>
/// Defines the version string for the plugin to use.
/// </summary>
/// <value>The version.</value>
String version { get; }
/// <summary>
/// Defines the name of the author of the plugin.
/// </summary>
/// <value>The author.</value>
String author { get; }
/// <summary>
/// Defines the name of the root of xml packets destined
/// the plugin to recognise as it's own.
/// </summary>
/// <value>The name of the XML root.</value>
String xmlRootName { get; }
/// <summary>
/// Defines the method that is used by the host service shell to pass request data
/// in XML to the plugin for processing.
/// </summary>
/// <param name="XMLData">String containing XML data containing the request.</param>
/// <returns>String holding XML data containing the reply to the request.</returns>
String processRequest(String XMLData);
/// <summary>
/// Defines the method used at shell startup to provide any one time initialisation
/// the client will call this once, and once only passing to it a host interface pointing to itself
/// that the plug shall use when calling methods in the IPluginHost interface.
/// </summary>
/// <param name="theHost">The IPluginHost interface relating to the parent shell program.</param>
/// <returns><c>true</c> if startup was successfull, otherwise <c>false</c></returns>
Boolean startup(IPluginHost theHost);
/// <summary>
/// Called by the shell service at shutdown to allow to close any resources used.
/// </summary>
/// <returns><c>true</c> if shutdown was successfull, otherwise <c>false</c></returns>
Boolean shutdown();
}
For the actual plugin interface. This needs to be referenced both by the client app, and any plugin that uses it.
You'll see one other interface mentioned, this is the Host interface for the plugin to call back to, if you don't need to use it for 2 way comms then you can strip it out, but in case it's needed:
public interface IPluginHost
{
/// <summary>
/// Defines a method to be called by plugins of the client in order that they can
/// inform the service of any events it may need to be aware of.
/// </summary>
/// <param name="xmlData">String containing XML data the shell should act on.</param>
void eventCallback(String xmlData);
}
Finally, to make a DLL that acts as a plugin, using a separate DLL project, and referencing the interfaces where needed, you can use the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using pluginInterfaces;
using System.IO;
using System.Xml.Linq;
namespace pluginSkeleton
{
/// <summary>
/// Main plugin class, the actual class name can be anything you like, but it MUST
/// inherit IPluginInterface in order that the shell accepts it as a hardware driver
/// module. The [PluginType] line is the custom attribute as defined in pluginInterfaces
/// used to define this plugins purpose to the shell app.
/// </summary>
[pluginType(pluginType.printer)]
public class thePlugin : IPluginInterface
{
private String _name = "Printer Plugin"; // Plugins name
private String _version = "V1.0"; // Plugins version
private String _author = "Shawty"; // Plugins author
private String _xmlRootName = "printer"; // Plugins XML root node
public string name { get { return _name; } }
public string version { get { return _version; } }
public string author { get { return _author; } }
public string xmlRootName { get { return _xmlRootName; } }
public string processRequest(string XMLData)
{
XDocument request = XDocument.Parse(XMLData);
// Use Linq here to pick apart the XML data and isolate anything in our root name space
// this will isolate any XML in the tags <printer>...</printer>
var myData = from data in request.Elements(this._xmlRootName)
select data;
// Dummy return, just return the data passed to us, format of this message must be passed
// back acording to Shell XML communication specification.
return request.ToString();
}
public bool startup(IPluginHost theHost)
{
bool result = true;
try
{
// Implement any startup code here
}
catch (Exception ex)
{
result = false;
}
return result;
}
public bool shutdown()
{
bool result = true;
try
{
// Implement any shutdown code here
}
catch (Exception ex)
{
result = false;
}
return result;
}
}// End class
}// End namespace
With a bit of work, you should be able to adapt all of this to do what you need, originally the project this was written was speced for dot net 3.5 and we did have it working in a windows service.
If you want to be more generic as possible, IMHO you should abstract UI layer too over pugins.
So the user actual interaction with UI exposed by Plugin (if there is any UI in it), like for Comments have to be a part of Plugin definition. The Host container has to provide a space where any plugin can push whatever he wants. The space requirement can be also a part of plugin descriptive manifest too. In this case Host, basically:
finds a plugin
loads it in memory
reads how much and what kind of space it needs
checks if specified space can be provided in this presice moment, if yes, allows plugin to fill its interface with plugins UI data.
And after or event pumping/user interaction is made by plugin itself.
This idea you can find, more or less on banners concepts in Web Development, or in mobile development, for example defining your app UI layout on Android.
Hope this helps.
I'm embedding IronPython (2.6.1) in a C# assembly and exposing several objects to scripts which are executed with PythonEngine.ExecuteFile. I expose them either with
scope.SetVariable("SomeObject", new SomeObject())
or
engine.Execute("from MyNamespace import SomeObject", scope)
depending on how the scripts use them. My application assembly is added to the engine with
engine.Runtime.LoadAssembly(Assembly.GetExecutingAssembly())
Now a script can execute help(SomeObject) and dump the nice little help info(*), however it's incomplete. None of the object's events or properties (public of course) show up and many of the 'built-in' members are missing as well.
Here's the odd part; If I fire up ipy.exe and execute the following:
import sys
sys.path.append('<location of my app>')
import clr
clr.AddReferenceToFile('myapp.exe')
from MyNamespace import SomeObject
help(SomeObject)
I get a different dump, complete with all the missing members!
Why do the two differ?
Bonus question: Assuming I get it working correctly, is it possible to add descriptive text on my CLR objects to the output of help()? Like you can from within the script, on your python-native types? My first guess was the DescriptionAttribute, but that didn't work.
(*) Obviously a final, working script wouldn't do this but it is exceedingly helpful while writing/testing the script.
Answered
Here is a complete console program that illustrates how to import the site which replaces the usless internal help() with the standard python library help().
using System;
using System.Collections.Generic;
using System.Reflection;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting.Hosting.Providers;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
// Work around issue w/ pydoc - piping to more doesn't work so instead indicate that we're a dumb terminal
if (Environment.GetEnvironmentVariable("TERM") == null)
Environment.SetEnvironmentVariable("TERM", "dumb");
var engine = Python.CreateEngine();
// Add standard Python library path (is there a better way to do this??)
PythonContext context = HostingHelpers.GetLanguageContext(engine) as PythonContext;
ICollection<string> paths = context.GetSearchPaths();
paths.Add(#"C:\Program Files (x86)\IronPython 2.6\Lib");
context.SetSearchPaths(paths);
// Import site module
engine.ImportModule("site");
engine.Runtime.LoadAssembly(Assembly.GetEntryAssembly());
var scope = engine.CreateScope();
scope.SetVariable("SomeObject", new SomeObject());
engine.Execute("help(SomeObject)", scope);
}
}
/// <summary>
/// Description of SomeObject.
/// </summary>
public class SomeObject
{
/// <summary>
/// Description of SomeProperty.
/// </summary>
public int SomeProperty { get; set; }
/// <summary>
/// Description of SomeMethod.
/// </summary>
public void SomeMethod() { }
/// <summary>
/// Description of SomeEvent.
/// </summary>
public event EventHandler SomeEvent;
}
}
My guess is that in your app you're not importing the standard library. IronPython includes a built-in help function and the standard library includes a help function which gets installed by site.py. If you make sure the standard library is available when you host and then import site.py when you create the engine then you should get the standard library help. That being said it's probably a bug or missing feature that the built-in help isn't documenting events and properties.
Regarding the documentation - yes, you just need to use C#'s doc comments and build with the /doc:MyAssemblyName.xml option. If the XML file is in the same directory as the assembly IronPython will read the doc strings and provide them for doc attributes which help() then reads.
I'd like to create a Window Service that can load several DLLs from a certain location and publish them using Remoting on a specific TCP port (let's say 9000).
Each DLL contains a single class that will published.
For example (Test.dll)
namespace Test
{
class Service
{
// methods here
}
}
The service should publish it using Remoting tcp://<servername>:9000/Test.dll
I know how to use Remoting but I'd like to know how to implement the service so that it can load the DLLs dynamically when starting and unload them when stopping.
May be there's another way to do this?
You can use the many methods in the Assembly class to achieve this, they're loaded into the AppDomain which will be unloaded with the service when you stop it.
Check out: http://msdn.microsoft.com/en-us/library/system.reflection.assembly.loadfile.aspx
You can easily load assemblies in the .NET framework. Take a look at the Assembly class' Load method for guidance on how to do this:
http://msdn.microsoft.com/en-us/library/xbe1wdx9%28v=VS.71%29.aspx
If you are looking for a robust solution, look into MEF, the Managed Extensibility Framework.
If, however, you just want to load the DLLs as simple plugins, I have some code samples from my TournamentApi:
/// <summary>
/// Provides methods to load plugins from external assemblies.
/// </summary>
public static class PluginLoader
{
...
/// <summary>
/// Loads the plugins from a specified assembly.
/// </summary>
/// <param name="assembly">The assembly from which to load.</param>
/// <returns>The plugin factories contained in the assembly, if the load was successful; null, otherwise.</returns>
public static IEnumerable<IPluginFactory> LoadPlugins(Assembly assembly)
{
var factories = new List<IPluginFactory>();
try
{
foreach (var type in assembly.GetTypes())
{
IPluginEnumerator instance = null;
if (type.GetInterface("IPluginEnumerator") != null)
{
instance = (IPluginEnumerator)Activator.CreateInstance(type);
}
if (instance != null)
{
factories.AddRange(instance.EnumerateFactories());
}
}
}
catch (SecurityException ex)
{
throw new LoadPluginsFailureException("Loading of plugins failed. Check the inner exception for more details.", ex);
}
catch (ReflectionTypeLoadException ex)
{
throw new LoadPluginsFailureException("Loading of plugins failed. Check the inner exception for more details.", ex);
}
return factories.AsReadOnly();
}
}
This takes a loaded assembly and instantiates an instance of each IPluginEnumerator in the assembly and has it return each IPluginFactory (Abstract Factory) that it supports.
Please feel free to take a look at the source code to the TournamentApi project for the rest of the code.