简体   繁体   中英

What Ninject convention should I use to bind all interfaces starting with "I" with interfaces having the same name without the "I" prefix for COMObj?

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 error Interop 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 the Interop.*.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 interop Interop.SpeechLib.dll assembly which is equal in concept to your AcoSDK.dll . On the SpeechLib reference, select Properties and set Embed Interop Types = False . Add the Ninject nuget package.

IKernel kernel = new StandardKernel();
var assemblyOfEmbeddedComTypes = typeof(ISpVoice).Assembly;
var allTypes = assemblyOfEmbeddedComTypes.GetTypes();
var comClassTypes = allTypes.Where(
    x => x.IsClass && 
    x.IsCOMObject && 
    x.GetInterfaces().Any()).ToList();
foreach (var iface in allTypes.Where(t => 
    t.IsInterface && 
    t.Name.StartsWith("I") && 
    t.GetCustomAttributes<ComImportAttribute>(false).Any()))
{
    var impl = comClassTypes.FirstOrDefault(x => x.GetInterfaces().Contains(iface));
    if (impl != null)
        kernel.Bind(iface).To(impl);
}
var sv2 = kernel.Get<ISpVoice>();
sv2.Speak("Hello World", 0, out _);

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) or Type.GetTypeFromProgID(string) that will give you a Type that will work with Activator.CreateInstance(type) . The CLSID is shown in your example in the GuidAttribute on the AcoSDKClass . 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 the GuidAttribute value a bit harder, but (spoiler alert) we can't use Types from this assembly anyway.

var assembly = Assembly.ReflectionOnlyLoadFrom("..\\..\\obj\\Debug\\Interop.SpeechLib.dll");
var types = assembly.GetTypes();
var interfaces = types.Where(t => t.IsInterface && t.Name.StartsWith("I") &&
     t.GetCustomAttributesData().Any(c => c.AttributeType == typeof(ComImportAttribute)));
var classes = types.Where(t => t.IsClass && t.IsCOMObject &&
     t.GetCustomAttributesData().Any(c=> c.AttributeType == typeof(GuidAttribute))).ToArray();
var typeNameToClsidDict = new Dictionary<string, Guid>();
foreach (var iface in interfaces)
{
    var c = classes.FirstOrDefault(x => x.GetInterfaces().Contains(iface));
    if (c != null)
    {
        var guidAtt= c.GetCustomAttributesData()
                    .First(ad => ad.AttributeType == typeof(GuidAttribute));
        var clsid = new Guid((string)guidAtt.ConstructorArguments.First().Value);
        typeNameToClsidDict.Add(iface.FullName, clsid);
    }
}

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:

IKernel kernel = new StandardKernel();
var assemblyOfEmbeddedComTypes = typeof(ISpVoice).Assembly;
foreach (var iface in assemblyOfEmbeddedComTypes.GetTypes().Where(
             t => t.IsInterface && 
             t.Name.StartsWith("I") &&
             t.GetCustomAttributes<ComImportAttribute>(false).Any()))
{
    if (typeNameToClsidDict.TryGetValue(iface.FullName, out var clsid))
    {
        var type = Type.GetTypeFromCLSID(clsid);
        kernel.Bind(iface).ToMethod(_ => Activator.CreateInstance(type));
    }
}
var sv2 = kernel.Get<ISpVoice>();
sv2.Speak("Hello World", 0, out _);

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM