- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我在实用程序库中遇到问题,它执行一些 COM 互操作。它保留对在调用之间使用的 COM 对象的引用。
如果所有方法都是从使用相同 COM 线程模型的线程调用的,则该类可以正常工作。
但是,如果创建 COM 对象的调用使用与后续调用不同的线程模型,QueryInterface 将失败并返回 E_NOINTERFACE
。
我们只有在将 async
分支添加到我们的单元测试时才发现这一点;在此之前,它在所有 MTA 应用程序和所有 STA 单元测试中运行良好......
我想我理解失败的原因(通过 COM docs 、 Chris Brumme's blog )——所使用的 COM 对象支持“两种”线程模型,这导致 C# 在 STA 和 MTA 创建的实例之间创建一个栅栏。
然而,从库的角度来看,我能想到的唯一修复有点垃圾:
CurrentThread.ApartmentState
)有没有更清洁/更简单的选择?这是一个 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 您愿意为非线程安全但无论如何都从工作线程使用的组件类提供什么样的支持。你在那里犯下了可怕的罪行。当您使用 [STAThread] 时,您就做出了一个 promise 。有两件事你必须做:你绝不能阻塞线程,你必须抽出一个消息循环(又名 Application.Run)。请注意您是如何违反两个 要求的。永远不要撒谎,当你这样做时会发生非常糟糕的事情。但您还没有做到这一点。
您可以从正在使用的组件类中获得的线程支持很容易发现。启动 Regedit.exe 并导航到 HKLM\Software\Wow6432Node\Classes\CLSID。找到您使用的 {guid} 并查看您在 InProcServer32 键中看到的 ThreadingModel 值。您正在使用的是“两者”。意味着它被编写为既可以从 STA 线程工作,也可以从根本不支持线程安全并在 MTA 中运行的线程工作。就像你的主线程和你的任务。正如您所发现的,两者都可以正常工作。请注意,这不是很常见,绝大多数 COM 服务器仅支持“单元”线程模型。 Microsoft 通常会不遗余力地支持这两者。
因此,您在 STA 线程上创建了枚举器对象,并在 MTA 中的线程上使用它。现在 COM 运行时必须做一些非常重要的事情,它必须确保可能从您调用的方法调用的任何回调(又名事件)在同一个 STA 线程上运行,以便任何代码在回调也是线程安全的。换句话说,它必须将来自工作线程的调用编码回您的主线程。相当于 .NET 应用程序中的 Control.Invoke 或 Dispatcher.Invoke。在 COM 中完全自动完成。
这需要做一些在 .NET 中非常容易但在非托管代码中非常困难的事情。该方法的参数必须从一个堆栈帧复制到另一个堆栈帧,以便可以在另一个线程上进行调用。由于反射,在 .NET 中很容易做到。对于非托管代码而言,这并不容易,它需要一个知道方法参数类型的 oracle,以替代丢失的元数据。
该 oracle 也在注册表中找到。使用 Regedit 并导航到 HKLM\Software\Wow6432Node\Classes\Interface 键。在那里找到接口(interface) guid,{29840822-5B84-11D0-BD3B-00A0C911CE86} 正如异常消息告诉您的那样。您会注意到问题:它不存在。是的,异常消息非常糟糕。报告真正的 E_NOINTERFACE 是因为 COM 运行时也找不到其他方法,不支持 IMarshal。如果它在那里,那么您将不得不处理 [STAThread] 谎言,您的线程将死锁。
顺便说一下,使用 ThreadingModel 为“Both”的 COM 对象模型几乎总是支持编码(marshal)处理。只是不适用于您尝试使用的特定。 DirectShow 在过去 10 年已被弃用,取而代之的是 Media Foundation。您找到了 Microsoft 决定停用它的一个很好的理由。
所以这只是您需要知道的事情。一个细节与必须知道 Random 类不是线程安全的没有太大区别。它在 MSDN 中没有很好的记录,但如前所述,您很容易发现它。
关于c# - 如何处理类库中的 COM QueryInterface 失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38228454/
我有一个非托管 C++ 类,其中有一个 com 映射。例如: BEGIN_COM_MAP (MyClass) COM_INTERFACE_ENTRY(...) END_COM_MAP 但是现在如果
问题: 我在代理上成功调用了 CoSetProxyBlanket(如果这是正确的术语),然后我在同一个代理上调用了 QueryInterface,但我收到了 0x80070005(“拒绝访问”)的结果
如果我有 IUnknown *ptr , 我需要调用Release()在我通过ptr->QueryInterface()获得的每个界面上, 另外调用ptr->Release()当我完成 ptr ? 我
我试图找到这个问题的答案,但完全没有运气。没有关于如何使用 Marshal.QueryInterface 返回的值的文档。 (在我的情况下是“pISomething”)。它是 IntPtr 的一个实例
考虑以下代码: TMyList = class(TList, IMyList) Delphi 向我显示错误: [DCC Error] test.pas(104): E2003 Undeclared i
我写了这段代码 'use strict'; module.exports = { up: (queryInterface, Sequelize) => { }, down: (queryI
假设我有一个实现两个或多个 COM 接口(interface)的类(与 here 完全一样): class CMyClass : public IInterface1, public IInterfa
IUnknown::QueryInterface() 被传递了一个 void** 参数,表示放置检索到的接口(interface)的地址。 STDMETHOD QueryInterface(/* [i
我想将此 USB 检测器转换为 Swift 3 https://gist.github.com/zachbadgett/471d72e83fee413d0f38 但是我卡在了这条线上: let dev
我正在尝试使用 Word 文档中的一些嵌入对象。较早的张贴者告诉我,这不是直截了当的。以下是链接答案的摘录: "As I mentioned earlier, utilizing the embedd
queryInterface.removeConstraint() 的文档没有记录可以传递给 removeConstraint() 方法的 options 对象。事实上,没有记录可以传递给各种 que
我在实用程序库中遇到问题,它执行一些 COM 互操作。它保留对在调用之间使用的 COM 对象的引用。 如果所有方法都是从使用相同 COM 线程模型的线程调用的,则该类可以正常工作。 但是,如果创建 C
是否可以为使用 ATL 的类提供 QueryInterface 的实现? BEGIN_COM_MAP(CConcrete) // Defines _InternalQueryInterface C
在我的应用程序中,我通过 CreateInstance 创建了一个对象 A,该对象又创建了一个对象 B。这两个对象应该存在于同一个进程中。 现在我看到对象 B 在请求某个接口(interface)时返
以上是否可行? 我可以这样做吗: IUnknown *punk; punk->QueryInterface(IID_MyInterface, (void**)&m_pMyInterface); 我认为
我正在尝试在现有 COM 组件中构建连接点。我从 CCmdTarget 派生类,并按照 MSDN 中的描述在类声明和实现中调用 MFC 宏。组件已正确构建、链接和运行。但是,当我调试我的客户端应用程序
这个问题在这里已经有了答案: Handling CoCreateInstance return value (2 个答案) 关闭 8 年前。 设想一种情况: CComPtr pGraph; HRES
我想知道是否有更简洁的方法来编写下面的(工作)代码: uint uEnum = 0; PStore.EnumTypes(0, 0, ref uEnum); System.Reflection.Memb
IUnknown::QueryInterface() 的典型实现方式如下:为每个支持的接口(interface) ID 使用一个 if-else-if 链并执行以下操作: if( iid == __u
我明白 QueryInterface 是什么方法实际上是这样做的——它只返回一个指向特定接口(interface)的指针。但我的问题是,我为什么要使用这种方法? 我的意思是,有什么区别 QueryIn
我是一名优秀的程序员,十分优秀!