CSDN技术中心 com之套间(house apartmentt)

1353人阅读
进程相当于一个小城镇,线程相当于这个城镇里的居民,STA(单线程套间)相当于居民房,是私有的,MTA(多线程套间)相当于旅馆,是公用的,Com对象相当于居民房或旅馆里的物品.接下去就好理解了,一个小城镇(进程)里可以有很多很多的(居民)线程,这个城镇(进程)只有一间旅馆(MTA),但可以有很多很多的居民房(STA).只有居民(线程)进入了房间(居民房或旅馆,STA或MTA)以后才能使用该房间里的物品(COM对象),居民房(STA)里的物品(COM对象)只能供这间房子的主人(创建该STA的线程)使用,其它居民(线程)不能访问.同样,只有入住到旅馆(MTA)里的居民(线程,可以有多个)才可以访问到旅馆(MTA)里的物品(com对象),但因为是公用的,所以要合理的分配(同步)才能不会产生混乱.
.NET支持两种线程模型:STA和MTA。
STA(single threaded apartments)。apartment只是一个逻辑上的概念,它可以包含一个或多个线程。一个AppDomain可以包括一个或多个apartment。STA是指该apartment中只能包含一个thread。
MTA(multi threaded apartments)。指该apartment中可以包含多个thread。
STA and MTA 之间最大的区别就是MTA 可以在同一个apartment 中使用所有的共享资源并发执行多个线程。 而多个STA虽然可以共享数据,但是不能并发执行线程,存在性能问题。
线程的创建:
当创建一个新的STA线程时,CLR会在该AppDomain中创建一个apartment和thread(从属于该apartment)。如果是创建MTA线程,则会CLR会检查该AppDomain是否存在一个存放MTA的apartment,如果存在仅创建该线程到该MTA中,否则就创建一个MTA和thread(从属于该apartment)。
我们可以设置线程的属性。例如 t.ApartmentState = ApartmentState.STA;
线程的使用区别:
我们应该仅仅在访问STA-based 的COM组件时才使用STA线程模式。可以在注册表的HKEY_CLASSES_ROOT\CLSID\{Class ID of the COM component} \InProcServer32 下查看到该COM的线程模式。如果该值是Apartment,则说明该COM只能以STA模式运行。其他的值有Free(MTA),Both(STA+MTA),Single(只能在一个单一的线程中执行)。
其他情况下,我们应该使用MTA的线程,虽然需要我们费心线程间资源的同步问题。
我现在想在一个windows form的程序中实现从某个word文档复制图片并保存的方案。
具体是:打开word文档,将图片信息复制到粘贴板中,然后从粘贴板中取得图片信息,再保存到本地目录中。
说明:(本来是放在代码下面的,无奈POST之后就被代码挡住不显示了)
如果在某个按钮的事件中,直接调用该方法,那么界面将变得没有响应。所以我们需要考虑使用多线程来解决这个问题。Thread t = new Thread(new TheardStart(CopyImages); t.Start();
如果是这样,则程序会发生错误.。要么显示出现异常,要么没异常但是Clipboard为空,取不到任何数据!为什么呢?
因为Word.Application 是Automation并且STA-Based,不能在没有指定ThreadApartment的线程中被调用。所以导致了各种错误,所以需要在t.Start();前面加上t.Apartment = ApartmentState.STA;这样就完全正常了。
对于MTA的多线程我们就见的比较多了,不再举例了。
另外一点不明白,我监视任务管理器发现,我在执行Thread t = new Thread(new TheardStart(CopyImages);t.Apartment = ApartmentState.STA; t.Start();之后该程序的进程中线程数从3个增加到6个,如果创建的是MTA的线程则只增加1。我的理解是STA线程为需要维护内部隐藏的窗口类和消息队列而增加的。
下面是实现方法:
1private void CopyImages()
2&&&&&&& {
3&&&&&&&&&&& Word.Application app =
4&&&&&&&&&&& Word.Document doc =
5&&&&&&&&&&&
6&&&&&&&&&&& app = new ApplicationClass();
7&&&&&&&&&&&
8&&&&&&&&&&& try
9&&&&&&&&&&& {
10&&&&&&&&&&&&&&& object fileName = @&E:\A.doc&;
11&&&&&&&&&&&&&&& doc = app.Documents.Open(ref fileName,ref missing,ref missing,ref missing,ref missing,ref missing,ref missing,ref missing,ref missing,ref missing,
12&&&&&&&&&&&&&&&&&&& ref missing,ref missing,ref missing,ref missing,ref missing,ref missing);
14&&&&&&&&&&&&&&& int count = doc.InlineShapes.C
15&&&&&&&&&&&&&&& for(int i=1;i&=i++)
16&&&&&&&&&&&&&&& {
17&&&&&&&&&&&&&&&&&&& doc.InlineShapes[i].Range.Copy();
18&&&&&&&&&&&&&&&&&&&
19&&&&&&&&&&&&&&&&&&& if (Clipboard.GetDataObject() != null)
20&&&&&&&&&&&&&&&&&&& {
21&&&&&&&&&&&&&&&&&&&&&&& IDataObject data = Clipboard.GetDataObject();
23&&&&&&&&&&&&&&&&&&&&&&& if (data.GetDataPresent(DataFormats.Bitmap))
24&&&&&&&&&&&&&&&&&&&&&&& {
25&&&&&&&&&&&&&&&&&&&&&&&&&&& Image image = (Image)data.GetData(DataFormats.Bitmap,true);
26&&&&&&&&&&&&&&&&&&&&&&&&&&& image.Save(&E:\\& + i.ToString() + &.jpg&,System.Drawing.Imaging.ImageFormat.Jpeg);
27&&&&&&&&&&&&&&&&&&&&&&& }
28&&&&&&&&&&&&&&&&&&&&&&& else
29&&&&&&&&&&&&&&&&&&&&&&& {
30&&&&&&&&&&&&&&&&&&&&&&&&&&& lst_Items.Items.Add(doc.Name + &;无正确图片数据&);
31&&&&&&&&&&&&&&&&&&&&&&& }
32&&&&&&&&&&&&&&&&&&& }
33&&&&&&&&&&&&&&&&&&& else
34&&&&&&&&&&&&&&&&&&& {
35&&&&&&&&&&&&&&&&&&&&&&& lst_Items.Items.Add(doc.Name + &;粘贴板为空&);
36&&&&&&&&&&&&&&&&&&& }
37&&&&&&&&&&&&&&& }
38&&&&&&&&&&&&&&&
39&&&&&&&&&&& }
40&&&&&&&&&&& catch(Exception ex)
41&&&&&&&&&&& {
42&&&&&&&&&&&&&&& lst_Items.Items.Add(doc.Name + &发生错误;& + ex.Message);
43&&&&&&&&&&& }
44&&&&&&&&&&& finally
45&&&&&&&&&&& {
46&&&&&&&&&&&&&&& if (doc != null)
47&&&&&&&&&&&&&&&&&&& doc.Close(ref missing,ref missing,ref missing);
48&&&&&&&&&&&&&&& if (app != null)
49&&&&&&&&&&&&&&&&&&& app.Quit(ref missing,ref missing,ref missing);
50&&&&&&&&&&& }
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:208256次
积分:2468
积分:2468
排名:第5336名
原创:24篇
转载:126篇
评论:35条
(1)(7)(3)(2)(2)(2)(15)(4)(3)(1)(2)(11)(3)(13)(6)(34)(12)(2)(29)[STAThread]属性和SetApartmentState(ApartmentState.STA)的区别是什么?
[问题点数:40分,结帖人u]
[STAThread]属性和SetApartmentState(ApartmentState.STA)的区别是什么?
[问题点数:40分,结帖人u]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关帖子推荐:
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。请解释COM中 apartment (套间)的含义
[问题点数:0分]
请解释COM中 apartment (套间)的含义
[问题点数:0分]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
本帖子已过去太久远了,不再提供回复功能。1126人阅读
中译本的Transactional COM+:Building Scalable Applications(Addison-Wesley,2001 年)相当之烂,COM+的内容本来就不是很容易懂,翻译者更是基本用金山之类的东西交差,如publisher居然直接就翻译成了出版商!总之读的人懵。懵完之后我最纳闷的是关于context的概念以及与之想对应套间等概念,尤其是涉及到用.Net写COM+服务的时候,CLR里头不是有这些个东西了吗,为什么还要SCM来监听管理?回头查查资料才发现这两者真不是一件事情。于是就想COM+服务被调用的时候SCM还要分配套间,对象池和维护分布式事务,这些其实CLR都内部包括了,但是CLR却不是全局注册的,要想使用COM+服务还得再把.net事务类转化成COM才行!&个人认为这个是过渡时期的做法毕竟肯定都得在托管效率才高啊。
COM+ 集成:.NET 企业服务如何帮助您构建分布式应用程序
摘录。。。。
在我深入研究这些问题之前,您需要了解两个有关 CLR 类与 COM+ 上下文之间关系的细节问题。首先,对派生自 ServicedComponent 的类实例的调用将在 COM+ 上下文边界被侦听。这些对象被称为上下文绑定。对非派生自 ServicedComponent 的类实例的调用将不在 COM+ 上下文边界被侦听。这些对象被称为上下文灵活 (context-agile)。CLR 对象默认情况下总是上下文灵活的。它们只有从 ServicedComponent 派生时才是上下文绑定的。(这仍然与 .NET 远程处理上下文无关,我等一下将对此进行讨论。)
摘录。。。。
ServicedComponent 类派生自 System.ContextBoundObject,而后者又派生自 System.MarshalByRefObject,System.MarshalByRefObject 是使得对象可以通过 .NET 远程处理基础结构访问的基类。在新的远程处理层上直接建立 CLR/COM+ 集成基础结构有两个好处。首先,现在您能够使用远程处理层内置的对 SOAP 的支持,访问使用 COM+ 运行时服务的远程对象。但是,您无法使用 SOAP 从一个进程向另一个进程中传播声明性事务(这也许并不是您希望的,但事情的原理就是这样)。其次,它为 COM+ 运行时服务完全根据 CLR 远程处理层崭新的上下文基础结构实现新版本提供了一种途径,而这是目前的 COM+ 无法使用的。这在将来的某个时候是会实现的。当实现了这一步时,服务将与 SOAP 或者任何其他的传送机制无缝集成,上下文结构将是完全可扩展的。到那时,CLR 将使得 COM+ 编程的许多细节变得更简单,这总可算得上是朝着正确方向迈出了一步。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:82078次
积分:1697
积分:1697
排名:第9188名
原创:85篇
评论:32条
(2)(11)(3)(2)(12)(3)(27)(22)(4)COM里面的线程模型应该是COM里面比较困难的一部分了,我自己也是花了很多时间,而且也还不是很懂。最近又重新看了一下线程模型,为了加深自己的学习,特地写下这篇文章。
MSDN上有很多信息,比如
这里面就有很多关于STA的信息。建议仔细阅读,特别是新手。
这里罗列几条重要的规则:
Rules for single-threaded apartments are simple, but it is important to follow them carefully:
o Every object should live on only one thread (within a single-threaded apartment).
o Initialize the COM library for each thread.
o Marshal all pointers to objects when passing them between apartments.
o Each single-threaded apartment must have a message loop to handle calls from other processes and apartments within the same process. Single-threaded apartments without objects (client only) also need a message loop to dispatch the broadcast messages that some applications use.
o DLL-based or in-process objects do not call the COM init instead, they register their threading model with the ThreadingModel named-value under the InprocServer32 key in the registry. Apartment-aware objects must also write DLL entry points carefully. There are special considerations that apply to threading in-process servers. For more information, see In-Process Server Threading Issues.
简单翻译一下:
STA的规则很简单,但是需要小心的遵守这些规则:
o 每一个STA COM 对象只能存在于一个线程中 (在一个STA套间内)
o 每一个线程都需要初始化COM库
o 在套间之间传递com对象指针的时候,需要列集(marshal)
o 每一个STA套间必须拥有一个消息循环,用来处理从其他进程或者当前进程的其他套间过来的消息。(后面一句没有理解,就不翻译了,以免误导。)其实,我个人感觉如果一个STA套间创建了一个COM对象,只要这个COM对象不传递到其他线程,消息循环是可以省略的。但是如果COM对象需要传递到其他进程,那么就必须创建一个消息循环。
o COM对象本身并不需要调用COM的初始化函数;相反,他们会把他们的线程模型放在注册表中的一个叫做InprocServer32的键下面。后面的也不是很了解。以后弄明白了再说。
还是用几个例子来说明吧。
简单的STA COM组件
先来创建一个VS solution,很简单,里面有2个工程,一个是console,一个是ATL工程。看上去就像:
然后给MyCom增加一个接口,其实就是用ATL向导来做的,很简单,但是还是截一些图吧,这样更加形象。
注意图3中,我们选择了Apartment类型,也就是STA.搜索一下注册表,会发现注册表中有一项来表示COM的线程模型,这个应该就是上面的规则5所说的吧。
到这里,我们就增加了一个ICircle接口了。这是个空接口,给它增加一个函数吧。还是傻瓜式的用ATL向导。
增加了一个很简单的函数,写几行代码吧,很简单,打一个log:
STDMETHODIMP CCircle::Draw(BSTR color)
// TODO: Add your implementation code here
WCHAR temp[100] = { 0 };
swprintf_s(temp, L&ICircle::Draw, color: %s, tid: %d\n&, color, ::GetCurrentThreadId());
OutputDebugStringW(temp);
return S_OK;
在测试程序里面,写几行代码,直接贴出来了,因为真的非常简单:
// TestCom.cpp : Defines the entry point for the console application.
#include &stdafx.h&
#include &atlbase.h&
#include &../MyCom/MyCom_i.h&
#include &../MyCom/MyCom_i.c&
void Test1()
WCHAR temp[100] = { 0 };
swprintf_s(temp, L&calling thread: %d\n&, ::GetCurrentThreadId());
OutputDebugStringW(temp);
CoInitialize(NULL);
CComPtr&ICircle& spC
if (SUCCEEDED(spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC)))
spCircle-&Draw(CComBSTR(L&red&));
CoUninitialize();
int _tmain(int argc, _TCHAR* argv[])
运行一下,我们可以得到这个结果:
OK, 到这里为止,我们就简单写了个COM组件,COM组件里有一个接口叫做ICircle,这个接口里面有个方法叫做Draw。然后在测试程序(console程序)里面调用了一下,得到上面的结果:
1. 调用线程用CoInitialize()初始化
2. 调用线程的号码是2872
3. COM组件的Draw函数里面也打印了线程号,也是2872.
在上面的代码里面,我们可以说是:
1. 我们创建了一个STA的COM组件
2. 我们创建了一个STA的调用环境。
通常我们提交COM的线程模型,其实指的是两方面:一个是客户程序的线程模式,一个是组件所支持的线程模式。客户程序的线程模式只有两种,单线程公寓(STA)和多线程公寓(MTA)。组件所支持的线程模式有四种:Single(单线程)、Apartment(STA)、Free(MTA)、Both(STA+MTA)。
注意,公寓和套间是同一个概念,这只是翻译而已,都是指apartment。
那么我们现在可以说,上面的例子是STA的客户程序调用STA COM组件。
STA客户程序调用STA COM组件
现在来分析一下这种情况的方方面面。首先我要推荐一篇文章,相当nice:
建议一个字一个字的读。在这篇文章里面提到了2点:
An STA object created inside an STA thread will reside in the same STA as its thread.&All objects inside an STA will receive method calls only from the thread of the STA.
很简单,意思是说如果一个STA线程创建了一个STA对象,那么这个对象就存在于当前这个创建它的线程里面。第二点是说一个STA套间里面所有的对象都只接收来自这个套间里面线程的方法调用。
用个图来表示就大概是这个样子:
一个进程里面可以有多个STA套间,每个STA套间里面可以有多个STA对象。
如果一个客户程序创建了一个STA套间,然后在这个套间里面调用STA对象,这是最简单的一种情况。这种情况下:
1. 客户程序直接调用STA对象;
2. STA COM对象运行在客户程序创建的STA套间的线程里面;
3. 像上面的例子,我们创建的STA COM对象并不会传递到其他线程,那么也就不需要消息循环了。(看上面的代码,根本没有消息循环)
OK, 我们再来看看另外一种情况,MTA客户程序来调用STA COM 对象。
MTA客户程序调用STA COM组件
稍微改一下客户程序,很是简单,就是把刚才的CoInitialize(NULL)改成了CoInitializeEx(NULL, COINIT_MULTITHREADED),代码如下,就是新增了Test2()函数。
// TestCom.cpp : Defines the entry point for the console application.
#include &stdafx.h&
#include &atlbase.h&
#include &../MyCom/MyCom_i.h&
#include &../MyCom/MyCom_i.c&
void Test1()
WCHAR temp[100] = { 0 };
swprintf_s(temp, L&STA calling thread: %d\n&, ::GetCurrentThreadId());
OutputDebugStringW(temp);
CoInitialize(NULL);
CComPtr&ICircle& spC
if (SUCCEEDED(spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC)))
spCircle-&Draw(CComBSTR(L&red&));
CoUninitialize();
void Test2()
WCHAR temp[100] = { 0 };
swprintf_s(temp, L&MTA calling thread: %d\n&, ::GetCurrentThreadId());
OutputDebugStringW(temp);
CoInitializeEx(NULL, COINIT_MULTITHREADED);
CComPtr&ICircle& spC
if (SUCCEEDED(spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC)))
spCircle-&Draw(CComBSTR(L&green&));
CoUninitialize();
int _tmain(int argc, _TCHAR* argv[])
跑一下,看到:
这次我们看到调用线程和COM运行线程不是同一个,这是为什么?
刚才推荐的文章里面讲的很清楚:All STA objects in a process which are created inside non-STA threads will reside in the default STA.
Default STA:秘密就在这里了,当客户程序创建一个STA COM对象的时候,系统发现当前套间并不是STA(因为我们使用了CoInitializeEx(NULL, COINIT_MULTITHREADED);来创建的,这是MTA套间。这样,系统就会创建一个Default STA来运行STA对象。这个过程无需程序员关心。
这段话很重要:Developers new to the world of COM Apartments please note well this intriguing phenomenon: that even though a call toCreateInstance() orCoCreateInstance() is made inside a thread, the resulting object can actually
be instantiated in another thread. This is performed transparently by COM behind the scenes. Please therefore take note of this kind of subtle maneuvering by COM especially during debugging.
大致意思是说:
刚刚接触COM套间的程序员需要注意这一点:在一个线程里面调用CreateInstance或者CoCreateInstance()创建了一个对象,但是有可能这个对象是在另外一个线程里面被实例化。这个过程是COM系统做的。请在debug的时候注意这一点。
其实这个过程可以用下面的图来说明。
需要重点记住的是:这种情况下,CoCreateInstance返回的并不是真正对象的指针,而是一个代理,proxy。
ok,现在我们知道了为什么COM对象运行线程和客户程序的线程不是同一个。原因很简单,就是因为STA COM对象是在一个default sta里面运行。MTA 客户是不可以直接运行STA COM 对象的。
稍微修改一下代码,再来确定一下这个问题,我们把代码改成如下,其实很简单就是起5个线程来调用Test2().
int _tmain(int argc, _TCHAR* argv[])
std::thread t1(Test2);
std::thread t2(Test2);
std::thread t3(Test2);
std::thread t4(Test2);
std::thread t5(Test2);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
运行结果:看的很清楚,COM对象是在同一个线程里面运行的。也就是COM系统创建的default STA。
MTA客户调用STA对象也讲完了。有个问题,那么如果是不同的COM 接口,又如何?事实胜于雄辩,测试一下就好了。新增一个接口IMyRect,同样增加一个函数Draw。
客户端程序改成这样:
// TestCom.cpp : Defines the entry point for the console application.
#include &stdafx.h&
#include &atlbase.h&
#include &thread&
#include &../MyCom/MyCom_i.h&
#include &../MyCom/MyCom_i.c&
void Test1()
WCHAR temp[100] = { 0 };
swprintf_s(temp, L&STA calling thread: %d\n&, ::GetCurrentThreadId());
OutputDebugStringW(temp);
CoInitialize(NULL);
CComPtr&ICircle& spC
if (SUCCEEDED(spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC)))
spCircle-&Draw(CComBSTR(L&red&));
CoUninitialize();
void Test2()
WCHAR temp[100] = { 0 };
swprintf_s(temp, L&MTA calling thread: %d\n&, ::GetCurrentThreadId());
OutputDebugStringW(temp);
CoInitializeEx(NULL, COINIT_MULTITHREADED);
CComPtr&ICircle& spC
if (SUCCEEDED(spCircle.CoCreateInstance(CLSID_Circle, NULL, CLSCTX_INPROC)))
spCircle-&Draw(CComBSTR(L&green&));
CoUninitialize();
void Test3()
WCHAR temp[100] = { 0 };
swprintf_s(temp, L&MTA calling thread (rect): %d\n&, ::GetCurrentThreadId());
OutputDebugStringW(temp);
CoInitializeEx(NULL, COINIT_MULTITHREADED);
CComPtr&IMyRect& spR
if (SUCCEEDED(spRect.CoCreateInstance(CLSID_MyRect, NULL, CLSCTX_INPROC)))
spRect-&Draw(CComBSTR(L&blue&));
CoUninitialize();
int _tmain(int argc, _TCHAR* argv[])
std::thread t1(Test2);
std::thread t2(Test2);
std::thread t3(Test2);
std::thread t4(Test3);
std::thread t5(Test3);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
结果如下:
&实际上,刚才推荐的文章里面有一句话:All STA objects in a process which are created inside non-STA threads will reside in the default STA.
STA客户调用STA COM组件
1. STA对象在客户创建的STA套间线程里面运行;
2. STA客户直接调用STA COM对象指针;
MTA客户调用STA COM组件
1. STA对象在default STA里面运行,如果有多个STA对象,它们统统在同一个default sta线程里面运行。
2. MTA客户调用STA COM对象的代理。
测试代码:
我们接下来就讲讲COM对象的跨线程传递和消息循环。

* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:57654次
积分:1653
积分:1653
排名:第9511名
原创:105篇
转载:27篇
评论:44条
(19)(29)(6)(3)(1)(1)(3)(1)(4)(2)(2)(3)(15)(12)(21)(11)

我要回帖

更多关于 house apartment 的文章

 

随机推荐