STA和MTA 之 COM和套间

2023-10-13 00:30
文章标签 com sta mta 套间

本文主要是介绍STA和MTA 之 COM和套间,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

STA和MTA 之 COM和套间

Overview
COM技术过时了吗?这句话也对也不对。从技术上讲,确实COM的使用率在下降,但是从思想上来说,COM的面向接口的思想正在被Java和.NET发扬光大。那我们还需要和COM打交道吗?这取决于你工作的领域。虽然现在微软的平台在慢慢向着.NET迁移,不过,在维护原有非托管代码,编写和Windows系统组件打交道的程序,以及使用CLR调用非托管代码的时候,COM或多或少都是不可避免的。与COM打交道就没法不谈到套间(Apartments)。套间是COM中一个非常有用然而也非常难以理解的一个概念,可以说COM中的很多问题都和套间有关,理解了套间,离完全理解COM就更近了一步,本文将分若干次讨论套间的基础知识以及在.NET中的应用。

什么是套间(Apartments)
套间是COM为了简化对象对多线程的支持而推出的一套机制,用于指定线程和COM对象的多线程特性,并且对不同特性的套间之间的调用提供同步支持,保证不同多线程特性的对象之间可以互相正确调用而不会引入同步问题,简化编程(实际上可能搞得更复杂了)。比如,如果某个对象编写的时候忘记考虑多线程,或者没有时间考虑,或者没有必要提供实现多线程的支持,这个时候可以将对象指定为STA,让COM自动管理对该对象的调用,保证对象可以被正确调用,即使是多线程的调用也会被串行化(依次调用,而非同时调用)。反之,如果一个对象支持多线程调用,那么它可以被标记为MTA,COM会允许对其进行多线程的调用。
对于套间需要注意下面几点:
1、套间并不是一个真实存在的一个区域,而是一个逻辑的概念
2、套间表明了位于套间的代码的多线程特性,决定了以下几点
    a.      代码本身允许单线程调用还是多线程调用
   b.      代码创建COM对象拿到的是Proxy还是原始的指针(关于Proxy请参见后面的Proxy一节)
    c.      代码调用同一套间的COM对象是通过原始指针,不同套间则通过Proxy
3. 线程必须属于某个套间,这表明了线程本身的多线程特性,也就是线程会对COM对象进行多线程的调用还是单线程的调用。比如,一个线程位于STA中,那么该线程只适合直接调用支持单线程调用的、同一套间的COM对象,其他COM对象则需要通过Proxy来间接调用,什么是Proxy后面会讲到。同时,线程所处的套间还决定了创建对象的时候所获得的对象是对象本身还是Proxy。线程在同一时间内只能属于一个套间或者不属于某个套间,但是线程可以在不同时间内属于不同的套间。典型的例子有,线程调用了CoInitialize,然后再调用CoUninitialize退出套间,之后又调用了CoInitialize进入了另外一个套间,此外,线程临时进入NTA也是一个例子。下面会讲到。
4.     COM对象也必须属于某个套间,同样的这决定了COM对象的多线程特性,和上面类似。COM对象不会从一个套间迁移到另外一个套间,如果这个套间被释放,那么这个对象也同时被释放,这个事实对于STA套间尤为重要。
5.     跨套间必须要通过Proxy,这是COM保证套间能够工作的基础。后面Proxy一节会谈到为什么是这样

套间(Apartments)的类型
常见的套间有STA和MTA,此外Win2000中引入了一种新的套间NTA。STA用于单线程,MTA用于多线程。而NTA则被称为线程无关(Thread-Neutral)的多线程。简单来讲,STA,MTA,NTA的区别请见下表:



在文章后面将详细解释各个套间的特点、区别以及编程的有关注意事项。

线程和套间
线程通过调用CoInitialize/CoInitializeEx进入套间,然后通过CoUninitialize退出套间。进入套间可能会导致套间被创建,同样CoUninitialize调用会导致套间被释放。CoInitialize和CoUninitialize的调用次数必须Match,类似AddRef/Release。
CoInitialize只能进入STA套间,而CoInitializeEx可以通过传入参数进入不同的套间,传入COINIT_APARTMENTHREADED进入STA,而传入COINIT_MULTITHREADED则进入MTA。当调用了CoInitialize/CoUnintiialize之后,线程便属于了这个套间,如果指定STA,那么新的STA总会被创建,如果指定的是MTA,那么如果MTA不存在的话将创建一个新的MTA。细心的朋友可能已经注意到了,上面提到了3种套间,那么NTA跑哪去了呢?其实一个线程并不能属于NTA,线程只可以临时进入NTA,NTA中只可以存在对象。

对象和套间
COM对象总是属于某个套间的。COM对象在注册表里面可以通过ThreadingModel属性指定对象所期望的套间类型,有效的值有:


需要说明的是,从套间角度来讲主STA和其他非主STA没有区别,只是特别指定是主STA而已。

线程套间和对象套间的关系
大家可以看到,线程也有套间,同时对象也有套间,那么这两者有何关系呢?这是一个比较Confusing的一个问题。事实上,简单来讲,对象的套间设置决定了对象所处的套间,而线程的套间决定了线程的套间。OK,看到这里你可能会说,这不是等于没说吗?呵呵,这确实是最本质的区别,然而,另外这两个套间的设置还决定了另外一点,即套间和对象是否兼容,是否处于同一套间。这很重要,因为这决定的了CoCreateInstance所返回的对象的指针是原始指针还是Proxy(这里讨论进程内的情况,进程外则总是Proxy)。举例来讲,如果线程的套间是STA,并且对象的套间也是STA,那么这个对象就被创建在线程所位于的STA中,反之,如果线程的套间是STA,而对象的套间是MTA,那么对象则被创建到唯一的MTA套间中,线程拿到的是对象的Proxy(代理),而非原始指针。代理的概念后面会讲到。
MSDN中有一张表,这里稍作修改,列在下面:


跨套间(Cross-Apartment),Proxy/Stub以及Marshalling
套间调用本套间内的对象不需要Proxy,则是直接调用,和普通C++的虚函数调用并无区别。COM强大的地方(也是不太容易理解的地方)在于可以通过Proxy来实现线程安全。我们还是用一个实际的例子来考虑这个问题,假如两个MTA线程同时调用一个STA中的对象A,这个对象因为位于STA中,因此它编写的时候没有考虑到多线程问题,因此需要保护。如果两个MTA线程同时通过A的指针pA来调用A的方法,显然这个时候是无法提供线程安全的保护的。COM的解决方案是,让这两个MTA线程拿到的对象A并非对象A本身,而是A的Proxy。所谓Proxy,指的是该对象并非是实际对象,而是一个代理,负责将调用转发到它所代理的对象A,代理本身并不执行实际操作。而在服务器端,有一段代码称之为Stub,负责接受Proxy发来的请求,并实际执行这个请求。换句话说,Proxy总是在客户端,而Stub则是在服务器端。

这篇关于STA和MTA 之 COM和套间的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/199509

相关文章

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法   消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法 [转载]原地址:http://blog.csdn.net/x605940745/article/details/17911115 消除SDK更新时的“

com.google.gson.JsonSyntaxException:java.lang.IllegalStateException异常

用Gson解析json数据的时候,遇到一个异常,如下图: 这个异常很简单,就是你的封装json数据的javabean没有写对,你仔细查看一下javabean就可以了 比如:我的解析的代码是             Gson gson = new Gson();             ForgetJson rb = gson.fromJson(agResult.mstrJson, For

The import com.google cannot be resolved

The import com.google cannot be resolved,报错: 第一感觉就是缺少jar包,因为项目用maven管理,所以在pom.xml中添加: <dependency>  <groupId>com.google.code.gson</groupId>  <artifactId>gson</artifactId>  <version>2.3.1</ver

Docker容器创建时,无法访问镜像源:Could not connect to archive.ubuntu.com:80

1.问题描述 当基于dockerfile创建容器时,遇到Could not connect to ...、Failed to fetch ...等异常时,大概原因是没有配置好容器创建所需的镜像源。这里以Ubuntu基础镜像源为例。 dockerfile内容 FROM ubuntuRUN apt update && apt install python3 -y && apt install

JavaBug系列- Failed to load driver class com.mysql.cj.jdbc.Driver in either of HikariConfig class load

JavaBug系列之Mysql驱动问题 Java医生一、关于错误信息二、如何解决问题 Java医生 本系列记录常见Bug,以及诊断过程和原因 Java/一对一零基础辅导/企业项目一对一辅导/日常Bug解决/代码讲解/毕业设计等 V:study_51ctofx 一、关于错误信息 APPLICATION FAILED TO START Description: Fai

关于 export HF_ENDPOINT=https://hf-mirror.com

# 使用 Hugging Face Hub 镜像:设置和应用场景 ## 引言 Hugging Face 是一个流行的机器学习模型托管平台,它提供了大量的预训练模型和易于使用的API。为了提高访问速度和降低延迟,Hugging Face 提供了镜像服务,用户可以通过设置环境变量 `HF_ENDPOINT` 来指定使用特定的镜像地址。本文将介绍如何设置 `HF_ENDPOINT` 环境变量,并探讨

兔子--Android Studio出现错误:Error:Execution failed for task ':myapp:dexDebug'. com.android.ide.common.pro

重点在:finished with non-zero exit value 2. 这里表明了有重复的内容存在。 由于:Android Studio中引入包的方式有如下2种:    compile 'com.android.support:support-v4:22.0.0'    compile files('libs/support-v

【上】java获取requestMapping上所有注解功能实现及取匿名注释类的值及 class com.sun.proxy.$Proxy140 转换出错

java获取requestMapping上所有注解功能实现及取匿名注释类的值及 class com.sun.proxy.$Proxy140 转换出错 1,多人相当然以为类似对象一样直接强转下就可以,结果迎来的是class com.sun.proxy.$Proxy140转换出错【想法很勇敢,现实很骨感】 //Class<A> operatorMappingAnnotationType// 错误

【spring】does not have member field ‘com.sun.tools.javac.tree.JCTree qualid

spring-in-action-6-samples 的JDK版本 最小是11,我使用 了22: jdk21 jdk22 都与lombok 不兼容,必须使用兼容版本, 否则报错: thingsboard 的大神解释了: java: java.lang.NoSuchFieldError: Class com

linux下 ping: unknown host www.baidu.com” 解决方法

问题现象 :   ping 和 telnet 都无法正常使用   而nslookup 可以正常解析到域名 $ ping  www.baidu.com  ping: unknown host  www.baidu.com $ telnet baidu.com 80  baidu.com/80: Name or service not known