I'm working on integrating an accounting system which objects are COM objects.
When binding one to one as follows, it works just fine.
IKernel kernel = new StandardKernel();
kernel.Bind<IAcoSDKX>().ToMethod(_ => { return new AcoSDKX(); }).InSingletonScope();
The situation I'm having is that both IAcoSDKX
and AcoSDKX
are interfaces, and the AcoSDKClass
is inaccessible to the consumer.
So I'm looking for a way to bind both interfaces together as only their spelling differ. Ont starts with an 'I', and other doesn't. So I'd like to come up with a conventional binding where even though I keep using unbound interfaces, Ninject knows what to bind it to when activating the objects through constructor injection.
Here's the try I came up with so far with no success.
kernel.Bind(services => services
.FromAssembliesMatching("AcoSDK.dll")
.SelectAllTypes()
.Where(t => t.Name.StartsWith("I") && t.IsInterface)
.BindDefaultInterfaces());
So I wonder, how to configure a convention binding using Ninject that could suit the actual need?
Basically the convention is to bind all interfaces starting with "I" with interfaces having the same name without the "I" prefix.
EDIT
After further searches, I found out that the types of AcoSDK.dll are embedded into my own assembly. Only types that are early loaded are bindable.
Besides, although I can new a COM Object interface, the Activator.CreateInstance won't initialize it under pretext that it is an interface. See objects declaration as follows:
namespace AcoSDK {
[ComImport]
[Guid("00000114-0000-000F-1000-000AC0BA1001"]
[TypeLibType(4160)]
public interface IAcoSDKX { }
[ComImport]
[Guid("00000114-0000-000F-1000-000AC0BA1001")]
[CoClass(typeof(AcoSDKClass))]
public interface AcoSDKX : IAcoSDKX { }
[ComImport]
[Guid("00000115-0000-000F-1000-000AC0BA1001")]
[TypeLibType(2)]
[ClassInterface(0)]
public class AcoSDKXClass : IAcoSDKX, AcoSDKX { }
}
public class Program() {
// This initializes the type.
IAcoSDKX sdk = new AcoSDKX();
// This also does.
IKernel kernel = new StandardKernel();
kernel.Bind<IAcoSDKX>().ToMethod(_ => new AcoSDKX());
// This won't activate because type is an interface.
IAcoSDKX sdk = Activator.CreateInstance(typeof(AcoSDKX));
// This says : Interop type AcoSDKXClass cannot be embedded. Use the applicable interface instead.
IAcoSDKX sdk = Activator.CreateInstance(typeof(AcoSDKXClass));
// Same message occurs when newing.
IAcoSDKX sdk = new AcoSDKClass();
}
Given this new information, anyone has a similar experience with COM Objects? I tried to new the AcoSDKClass
or to access it in any fashion, and I just don't seem to get a hook on it.
A lot of this answer depends on how you are referencing the COM object. If you use the 'Add Reference' in Visual Studio to a COM class it will add the reference and generate an
Interop.*.dll
file. By default, if you view properties on the reference, Embed Interop Types = True. This is how you are configured given the compiler errorInterop type AcoSDKXClass cannot be embedded.
you note above. If you can change this to False it can save a lot of pain, but you need to ship theInterop.*.dll
file.First, I will answer the question as if you specified Embed Interop Types = False because it is simple. For this code, I'm using a console application with .NET 4.8. Then add a reference to a COM object - I'm using 'Microsoft Speech Object Library', which will generate a new reference
SpeechLib
which generates an interopInterop.SpeechLib.dll
assembly which is equal in concept to yourAcoSDK.dll
. On theSpeechLib
reference, select Properties and set Embed Interop Types = False. Add the Ninject nuget package.If you MUST work with Embed Interop Types = True, you would still need to have the full interop assembly available at runtime, because in order to create an instance of the COM class, you need to be able to get either the CLSID or ProgId of the COM class. You can use
Type.GetTypeFromCLSID(Guid)
orType.GetTypeFromProgID(string)
that will give you aType
that will work withActivator.CreateInstance(type)
. The CLSID is shown in your example in theGuidAttribute
on theAcoSDKClass
. Unfortunately, with the interop types embedded, this class will not be included in your assembly therefore cannot be found this way.For this code, set Embed Interop Types = True and erase the previous code. Since it is embedded, the interop is in the obj rather than the bin folder. The following looks in that interop assembly for all interfaces with your pattern, finds the first class that implements it (that has a Guid attribute which is the COM CLSID), then we add the interface name to a dictionary along with the CLSID. You don't need to technically need to use
ReflectionOnlyLoadFrom
here as it makes getting theGuidAttribute
value a bit harder, but (spoiler alert) we can't use Types from this assembly anyway.This is where the real fun is. Since Embed Interop Types = true, the COM interface types are from YOUR assembly and NOT the interop assembly - effectively they are different types because they are from different assemblies. So now you need to use reflection to find the embedded interfaces from your assembly - these are the types you need to register with Ninject - then lookup the CLSID from the dictionary we built from the interop assembly. Add the following code to what we did above:
I don't think there is a way to structure the Ninject conventions binding to do it fluently for either approach, though I may be wrong here.