Какое соглашение Ninject я должен использовать для привязки всех интерфейсов, начинающихся с «I», к интерфейсам с одинаковым именем без префикса «I» для ComObj?

#c# #ninject #comobject #ninject-conventions

Вопрос:

Я работаю над интеграцией системы учета, объектами которой являются COM-объекты.

При привязке одного к другому следующим образом это работает просто отлично.

 IKernel kernel = new StandardKernel();
kernel.Bind<IAcoSDKX>().ToMethod(_ => { return new AcoSDKX(); }).InSingletonScope();
 

Ситуация, в которой я нахожусь, заключается в том, что оба IAcoSDKX и AcoSDKX являются интерфейсами, и AcoSDKClass они недоступны потребителю.

Поэтому я ищу способ связать оба интерфейса вместе, поскольку различается только их написание. Онт начинается с «Я», а другой-нет. Поэтому я хотел бы придумать обычную привязку, в которой, хотя я продолжаю использовать несвязанные интерфейсы, Ninject знает, к чему его привязывать при активации объектов с помощью инъекции конструктора.

Вот попытка, которую я предпринял до сих пор, но безрезультатно.

 kernel.Bind(services => services
      .FromAssembliesMatching("AcoSDK.dll")
      .SelectAllTypes()
      .Where(t => t.Name.StartsWith("I") amp;amp; t.IsInterface)
      .BindDefaultInterfaces());
 

Поэтому мне интересно, как настроить привязку соглашения с помощью Ninject, которая могла бы удовлетворить фактическую потребность?

В основном соглашение заключается в том, чтобы связать все интерфейсы, начинающиеся с «I», с интерфейсами, имеющими одно и то же имя без префикса «I».

Редактировать

После дальнейших поисков я выяснил, что типы AcoSDK.dll встроены в мою собственную сборку. Привязываются только типы, которые загружены рано.

Кроме того, хотя я могу создать интерфейс COM-объекта, Активатор.CreateInstance не будет инициализировать его под предлогом того, что это интерфейс. См. Объявление объектов следующим образом:

 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();  
}
 

Учитывая эту новую информацию, у кого-нибудь есть подобный опыт работы с COM-объектами? Я пытался создать AcoSDKClass или получить к нему доступ любым способом, и, похоже, я просто не зацепился за него.

Ответ №1:

Многое из этого ответа зависит от того, как вы ссылаетесь на COM-объект. Если вы используете «Добавить ссылку» в Visual Studio в класс COM, он добавит ссылку и создаст Interop.*.dll файл. По умолчанию, если вы просматриваете свойства в ссылке, Внедрите типы взаимодействия = True. Именно так вы настроены, учитывая ошибку компилятора Interop type AcoSDKXClass cannot be embedded. , которую вы отметили выше. Если вы можете изменить это значение на False, это может сэкономить много времени, но вам нужно отправить Interop.*.dll файл.

Во-первых, я отвечу на вопрос так, как если бы вы указали Embed Interop Types = False, потому что это просто. Для этого кода я использую консольное приложение с .NET 4.8. Затем добавьте ссылку на COM — объект-я использую «Библиотеку речевых объектов Microsoft», которая создаст новую ссылку SpeechLib , которая создаст Interop.SpeechLib.dll сборку взаимодействия, которая по своей концепции равна вашей AcoSDK.dll . В SpeechLib ссылке выберите Свойства и установите Тип взаимодействия встраивания = False. Добавьте пакет Ninject nuget.

 IKernel kernel = new StandardKernel();
var assemblyOfEmbeddedComTypes = typeof(ISpVoice).Assembly;
var allTypes = assemblyOfEmbeddedComTypes.GetTypes();
var comClassTypes = allTypes.Where(
    x => x.IsClass amp;amp; 
    x.IsCOMObject amp;amp; 
    x.GetInterfaces().Any()).ToList();
foreach (var iface in allTypes.Where(t => 
    t.IsInterface amp;amp; 
    t.Name.StartsWith("I") amp;amp; 
    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 _);
 

Если вам НЕОБХОДИМО работать с типами взаимодействия Embed = True, вам все равно потребуется полная сборка взаимодействия, доступная во время выполнения, потому что для создания экземпляра класса COM вам необходимо получить либо идентификатор CLSID, либо идентификатор ProgID класса COM. Вы можете использовать Type.GetTypeFromCLSID(Guid) или Type.GetTypeFromProgID(string) это даст вам Type то, с чем вы будете работать Activator.CreateInstance(type) . Идентификатор CLSID показан в вашем примере в разделе GuidAttribute на AcoSDKClass . К сожалению, со встроенными типами взаимодействия этот класс не будет включен в вашу сборку, поэтому его невозможно найти таким образом.

Для этого кода установите Embed Interop Types = True и удалите предыдущий код. Поскольку он встроен, взаимодействие находится в папке obj, а не в папке bin. Следующее выглядит в этой сборке взаимодействия для всех интерфейсов с вашим шаблоном, находит первый класс, который его реализует (у которого есть атрибут Guid, который является идентификатором COM CLSID), затем мы добавляем имя интерфейса в словарь вместе с идентификатором CLSID. Вам не нужно технически использовать ReflectionOnlyLoadFrom здесь, так как это немного усложняет получение GuidAttribute значения, но (предупреждение о спойлере) мы все равно не можем использовать типы из этой сборки.

 var assembly = Assembly.ReflectionOnlyLoadFrom("..\..\obj\Debug\Interop.SpeechLib.dll");
var types = assembly.GetTypes();
var interfaces = types.Where(t => t.IsInterface amp;amp; t.Name.StartsWith("I") amp;amp;
     t.GetCustomAttributesData().Any(c => c.AttributeType == typeof(ComImportAttribute)));
var classes = types.Where(t => t.IsClass amp;amp; t.IsCOMObject amp;amp;
     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);
    }
}
 

Вот где настоящее веселье. Поскольку Embed Interop Types = true, типы COM — интерфейсов взяты из ВАШЕЙ сборки, а НЕ из сборки взаимодействия-фактически это разные типы, потому что они из разных сборок. Итак, теперь вам нужно использовать отражение, чтобы найти встроенные интерфейсы из вашей сборки — это типы, которые вам нужно зарегистрировать в Ninject, — затем найдите идентификатор CLSID в словаре, который мы создали из сборки взаимодействия. Добавьте следующий код к тому, что мы сделали выше:

 IKernel kernel = new StandardKernel();
var assemblyOfEmbeddedComTypes = typeof(ISpVoice).Assembly;
foreach (var iface in assemblyOfEmbeddedComTypes.GetTypes().Where(
             t => t.IsInterface amp;amp; 
             t.Name.StartsWith("I") amp;amp;
             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 _);
 

Я не думаю, что есть способ структурировать девятипроектные соглашения, обязывающие делать это свободно для любого подхода, хотя здесь я могу ошибаться.

Комментарии:

1. Спасибо за ответ на туроу. Я проверю это, когда смогу (как можно скорее), и предоставлю надлежащую обратную связь.