0

我正在集成一个对象是 COM 对象的会计系统。

当如下一对一绑定时,它工作得很好。

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

我遇到的情况是IAcoSDKXAcoSDKX都是接口,AcoSDKClass消费者无法访问。

所以我正在寻找一种将两个接口绑定在一起的方法,因为它们的拼写不同。Ont 以“I”开头,而其他则没有。所以我想提出一个传统的绑定,即使我一直使用未绑定的接口,Ninject 也知道在通过构造函数注入激活对象时将其绑定到什么。

这是我到目前为止没有成功的尝试。

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

所以我想知道,如何使用 Ninject 配置一个可以满足实际需要的约定绑定?

基本上,约定是将所有以“I”开头的接口与具有相同名称但没有“I”前缀的接口绑定。

编辑

经过进一步搜索,我发现AcoSDK.dll的类型嵌入到我自己的程序集中。只有提前加载的类型是可绑定的。

此外,虽然我可以新建一个 COM 对象接口,但Activator.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任何方式更新或访问它,但我似乎没有得到它的钩子。

4

1 回答 1

0

这个答案很大程度上取决于您如何引用 COM 对象。如果您使用 Visual Studio 中的“添加引用”到 COM 类,它将添加引用并生成一个Interop.*.dll文件。默认情况下,如果您查看参考上的属性,则Embed Interop Types = TrueInterop type AcoSDKXClass cannot be embedded.鉴于您在上面提到的编译器错误,这就是您的配置方式。如果您可以将其更改为False它可以节省很多痛苦,但您需要发送Interop.*.dll文件。

首先,我将像您指定Embed Interop Types = False一样回答这个问题,因为它很简单。对于此代码,我使用的是带有 .NET 4.8 的控制台应用程序。然后添加对 COM 对象的引用 - 我正在使用“Microsoft Speech Object Library”,它将生成一个新引用,该引用SpeechLib生成一个Interop.SpeechLib.dll在概念上与您的AcoSDK.dll. 在SpeechLib参考上,选择 Properties 并设置Embed Interop Types = False。添加 Ninject nuget 包。

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 _);

如果您必须使用Embed Interop Types = True,您仍然需要在运行时提供完整的互操作程序集,因为为了创建 COM 类的实例,您需要能够获取 CLSID 或 ProgId COM 类。您可以使用Type.GetTypeFromCLSID(Guid)or Type.GetTypeFromProgID(string)that will give you a Typethat will work with Activator.CreateInstance(type). CLSID 显示在您的示例GuidAttributeAcoSDKClass。不幸的是,嵌入互操作类型后,此类将不会包含在您的程序集中,因此无法通过这种方式找到。

对于此代码,设置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 && 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);
    }
}

这才是真正有趣的地方。由于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 && 
             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 _);

我不认为有一种方法可以构建 Ninject 约定绑定以流畅地执行任何一种方法,尽管我在这里可能是错的。

于 2021-10-28T02:14:30.100 回答