Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/285.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何处理类库中的COM查询接口故障_C#_Com Interop - Fatal编程技术网

C# 如何处理类库中的COM查询接口故障

C# 如何处理类库中的COM查询接口故障,c#,com-interop,C#,Com Interop,我在实用程序库中遇到了一个问题,它执行一些COM互操作。它保留对调用之间使用的COM对象的引用 如果使用相同的COM线程模型从线程调用所有方法,则该类工作正常 但是,如果创建COM对象的调用使用与后续调用不同的线程模型,QueryInterface将失败,并出现E\u NOINTERFACE 我们只有在向单元测试添加async分支时才发现这一点;在此之前,它在所有MTA应用程序和所有STA单元测试中运行良好 我想我理解失败的原因(通过),正在使用的COM对象支持“两种”线程模型,这导致C#在ST

我在实用程序库中遇到了一个问题,它执行一些COM互操作。它保留对调用之间使用的COM对象的引用

如果使用相同的COM线程模型从线程调用所有方法,则该类工作正常

但是,如果创建COM对象的调用使用与后续调用不同的线程模型,QueryInterface将失败,并出现
E\u NOINTERFACE

我们只有在向单元测试添加
async
分支时才发现这一点;在此之前,它在所有MTA应用程序和所有STA单元测试中运行良好

我想我理解失败的原因(通过),正在使用的COM对象支持“两种”线程模型,这导致C#在STA和MTA创建的实例之间创建围栏

然而,从图书馆的角度来看,我能想到的唯一修复方法是一点垃圾:

  • 将此库仅用于MTA线程作为一条不成文的规则
  • 更改库以检测来自STA线程的调用并失败(例如使用
    CurrentThread.ApartmentState
  • 更改库以为所有COM互操作创建自己的MTA线程(或者仅当传入呼叫位于STA线程上时)
是否有更干净/更简单的选择?这是一个MCVE:

class Program
{
    [ComImport, Guid("62BE5D10-60EB-11d0-BD3B-00A0C911CE86")] class SystemDeviceEnum { };
    [ComVisible(true), ComImport, Guid("29840822-5B84-11D0-BD3B-00A0C911CE86"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface ICreateDevEnum { [PreserveSig] int CreateClassEnumerator([In] ref Guid pType, [Out] out IEnumMoniker ppEnumMoniker, [In] int dwFlags); }
    static ICreateDevEnum createDeviceEnum;
    static Guid VideoInputDeviceCategory = new Guid("860BB310-5D01-11d0-BD3B-00A0C911CE86");
    static void Prepare()
    {
        var coSystemDeviceEnum = new SystemDeviceEnum();
        createDeviceEnum = (ICreateDevEnum)coSystemDeviceEnum;
    }
    static int GetDeviceCount()
    {
        IEnumMoniker enumMoniker;
        createDeviceEnum.CreateClassEnumerator(ref VideoInputDeviceCategory, out enumMoniker, 0);
        if (enumMoniker == null) return 0;
        int count = 0;
        IMoniker[] moniker = new IMoniker[1];
        while (enumMoniker.Next(1, moniker, IntPtr.Zero) == 0) count++;
        return count;
    }
    [STAThread] 
    static void Main(string[] args)
    {
        RunTestAsync().Wait();
    }
    private static async Task RunTestAsync()
    {
        Prepare();        
        await Task.Delay(1);
        var count = GetDeviceCount();
        Console.WriteLine(string.Format("{0} video capture device(s) found", count));
    }    
}

众所周知,对COM线程的理解很差。实际上,要比线程化.NET类容易得多。几乎每个人都知道,比如说,List或Random类不是线程安全的。不是很多人知道如何以线程安全的方式使用它们。COM设计人员有更高的目标,他们认为程序员通常不知道如何编写线程安全的代码,聪明的人应该注意这一点

它确实需要注意一些细节。首先也是最重要的一点,您必须告诉COM您愿意为非线程安全的coclass提供什么样的支持,但这些coclass是从工作线程使用的。你在那里犯下了可怕的罪行。当你使用[StatThread]时,你就做出了一个承诺。您必须做两件事:决不能阻塞线程,必须泵送消息循环(aka Application.Run)。注意你是如何打破这两个要求的。永远不要说谎,当你说谎的时候会发生非常糟糕的事情。但你还没走那么远

您所使用的coclass提供的线程支持很容易发现。启动Regedit.exe并导航到HKLM\Software\Wow6432Node\Classes\CLSID。找到您使用的{guid},并查看您在InProcServer32键中看到的ThreadingModel值。对于您正在使用的一个,它是“两者”。这意味着它是从STA线程和完全不支持线程安全并在MTA中运行的线程编写的。就像你的主线和任务一样。正如你所发现的,这两种方法都很好。请注意,这并不常见,绝大多数COM服务器只支持“单元”线程模型。微软通常会多花几千英里来支持两者

因此,您在STA线程上创建了枚举器对象,并在MTA中的线程上使用它。现在COM运行时必须做一些非常重要的事情,它必须确保从您调用的方法调用的任何回调(也称为事件)在同一个STA线程上运行,以便回调中的任何代码也是线程安全的。换句话说,它必须将工作线程的调用封送回主线程。与.NET应用程序中的Control.Invoke或Dispatcher.Invoke等效。在COM中完全自动完成

这需要做一些在.NET中非常简单但在非托管代码中非常困难的事情。方法的参数必须从一个堆栈帧复制到另一个堆栈帧,以便可以在另一个线程上进行调用。由于反射,在.NET中很容易实现。对于非托管代码来说,这并不容易,它需要一个知道方法参数类型的oracle来替代缺少的元数据

该oracle也可以在注册表中找到。使用Regedit并导航到HKLM\Software\Wow6432Node\Classes\Interface键。如异常消息所示,在此处找到接口guid{29840822-5B84-11D0-BD3B-00A0C911CE86}。您会注意到问题:它不在那里。是的,异常消息非常糟糕。报告真正的E_NOINTERFACE是因为COM运行时也找不到其他方法,不支持IMarshal。如果它在那里,那么您就要处理[StatThread]谎言,您的线程将死锁

顺便说一句,使用ThreadingModel“两者”的COM对象模型几乎总是支持封送处理。只是不适用于您尝试使用的特定对象。DirectShow在过去的10年中被贬低,被媒体基金会取代。你找到了微软决定让它退役的一个很好的理由

这是你需要知道的。这个细节与必须知道Random类不是线程安全的没有多大区别。MSDN中没有很好的文档记录,但正如所指出的,很容易自己发现