裤子口袋简单做法:COM套间对.NET程序使用COM对象的影响

来源:百度文库 编辑:九乡新闻网 时间:2024/05/06 11:51:22
COM套间对.NET程序使用COM对象的影响
在COM时代里,套间是用来简化多线程环境下使用COM对象的,然而在.NET里面,微软又放弃了套间的概念,这样给我们在.NET里面使用COM对象的时候造成了很多的麻烦。例如有的时候你会发现在有的线程里面创建了COM对象并将它的引用保存在全局变量里面,在其他的线程里面使用的时候,却发现.NET扔出一个InvalidCastException的异常,发生这种情况大多数都是因为两个.NET线程运行在不同的套间引起的。比如下面的COM服务器和C#客户端:
C#客户端的源代码
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Runtime.InteropServices;
5. using System.Text;
6. using System.Diagnostics;
7. using System.Security.Cryptography;
8. using System.Security.Principal;
9. using Microsoft.Win32.SafeHandles;
10. using System.ComponentModel;
11. using System.Reflection;
12. using System.Security;
13. using System.IO;
14. using System.Threading;
15. using System.Security.Permissions;
16.
17. using ApartmentComponentLib;
18.
19. namespace CSharpQuestions
20. {
21.     public class Watcher
22.     {
23.         private object m_IStaObject = null;
24.
25.         [STAThread]
26.         public static void Main()
27.         {
28.             Console.WriteLine(Thread.CurrentThread.GetApartmentState());
29.             Watcher watcher = new Watcher();
30.             watcher.Initialize();
31.             watcher.CreateThreads().Join();
32.
33.             Console.WriteLine("Press any key");
34.             Console.ReadLine();
35.         }
36.
37.         private Thread CreateThreads()
38.         {
39.             Thread thread = new Thread(ThreadFunc);
40.             thread.Start();
41.
42.             return thread;
43.         }
44.
45.         private void ThreadFunc()
46.         {
47.             Console.WriteLine(Thread.CurrentThread.GetApartmentState());
48.             IStaObject2 obj = (IStaObject2)m_IStaObject;
49.             obj.TestMethod();
50.        }
51.
52.         private void Initialize()
53.         {
54.             m_IStaObject = new StaObject2Class();
55.         }
56.     }
57. }
COM服务器端
IDL文件
1. import "oaidl.idl";
2. import "ocidl.idl";
3.
4. [
5.     object,
6.     uuid(34CF395D-F7F8-41FC-9074-E966304DA425),
7.     dual,
8.     nonextensible,
9.     helpstring("IStaObject Interface"),
10.    pointer_default(unique)
11. ]
12. interface IStaObject : IDispatch{
13.    [id(1), helpstring("method TestMethod")] HRESULT TestMethod(void);
14. };
15. [
16.    object,
17.    uuid(2451960E-F141-4F09-AB71-124E62B6A25E),
18.    helpstring("IStaObject2 Interface"),
19.    pointer_default(unique)
20. ]
21. interface IStaObject2 : IUnknown{
22.    HRESULT TestMethod(void);
23. };
24. [
25.    uuid(E410D347-D200-4362-82B8-F3361FA54446),
26.    helpstring("ApartmentComponentLib Type Library")
27. ]
28. library ApartmentComponentLib
29. {
30.    importlib("stdole2.tlb");
31.    [
32.           uuid(2C0624C9-4C88-4114-A165-9E4AA59A241F),
33.           helpstring("StaObject Class")
34.    ]
35.    coclass StaObject
36.    {
37.           [default] interface IStaObject;
38.    };
39.    [
40.           uuid(274BDE35-680D-48DC-A2F3-3AE26E7700DA),
41.           helpstring("StaObject2 Class")
42.    ]
43.    coclass StaObject2
44.    {
45.           [default] interface IStaObject2;
46.    };
47. };
头文件
1. // StaObject2.h : Declaration of the CStaObject2
2.
3. #pragma once
4. #include "resource.h"       // main symbols
5.
6. #include "ApartmentComponent_i.h"
7.
8. #if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
9. #error "Single-threaded COM objects are not properly supported on Windows CE platform"
10. #endif
11.
12. // CStaObject2
13. class ATL_NO_VTABLE CStaObject2 :
14.    public CComObjectRootEx,
15.    public CComCoClass,
16.    public IStaObject2
17. {
18. public:
19.    CStaObject2()
20.    {
21.    }
22.
23. DECLARE_REGISTRY_RESOURCEID(IDR_STAOBJECT2)
24.
25.
26. BEGIN_COM_MAP(CStaObject2)
27.    COM_INTERFACE_ENTRY(IStaObject2)
28. END_COM_MAP()
29.
30.
31.
32.    DECLARE_PROTECT_FINAL_CONSTRUCT()
33.
34.    HRESULT FinalConstruct()
35.    {
36.           return S_OK;
37.    }
38.
39.    void FinalRelease()
40.    {
41.    }
42.
43. public:
44.    STDMETHOD(TestMethod)(void);
45.
46. };
47.
48. OBJECT_ENTRY_AUTO(__uuidof(StaObject2), CStaObject2)
CPP文件
1. #include "stdafx.h"
2. #include "StaObject2.h"
3. #include
4.
5. using namespace std;
6.
7. // CStaObject
8. STDMETHODIMP CStaObject2::TestMethod(void)
9. {
10.    cout << "CStaObject2::TestMethod" << endl;
11.
12.    return S_OK;
13. }
Rgs文件
1. HKCR
2. {
3.     ApartmentComponent.StaObject2.1 = s 'StaObject2 Class'
4.     {
5.            CLSID = s '{274BDE35-680D-48DC-A2F3-3AE26E7700DA}'
6.     }
7.     ApartmentComponent.StaObject2 = s 'StaObject2 Class'
8.     {
9.            CLSID = s '{274BDE35-680D-48DC-A2F3-3AE26E7700DA}'
10.           CurVer = s 'ApartmentComponent.StaObject2.1'
11.    }
12.    NoRemove CLSID
13.    {
14.           ForceRemove {274BDE35-680D-48DC-A2F3-3AE26E7700DA} = s 'StaObject2 Class'
15.           {
16.                  ProgID = s 'ApartmentComponent.StaObject2.1'
17.                  VersionIndependentProgID = s 'ApartmentComponent.StaObject2'
18.                  InprocServer32 = s '%MODULE%'
19.                  {
20.                        val ThreadingModel = s 'Free'
21.                  }
22.                  'TypeLib' = s '{E410D347-D200-4362-82B8-F3361FA54446}'
23.           }
24.    }
25. }
将COM 服务器注册,然后使用tlbimp.exe生成一个可以被C#客户端程序引用的IA(Interop Assembly), 并且编译运行上面的C#客户端程序,你会发现.NET会抛出一个System.InvalidCastException异常:
System.InvalidCastException occurred
Message="Unable to cast COM object of type 'ApartmentComponentLib.StaObject2Class' to interface type 'ApartmentComponentLib.IStaObject2'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{2451960E-F141-4F09-AB71-124E62B6A25E}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE))."
Source="ApartmentComponentLib"
StackTrace:
at ApartmentComponentLib.StaObject2Class.TestMethod()
InnerException:
从高亮显示的消息里面可以看出,当我们试图在另外一个线程使用另一个线程创建的对象的时候,查询所需要的COM接口失败—也就是为什么.NET扔出来一个InvalidCastException。
这就是一个典型的跨套间使用COM对象失败的例子,因为跨套间使用COM对象时,COM要求所使用的COM接口是可列集(Marshal)的。如果所要求(QueryInterface)的接口不能被列集(Marshal),在调用端那里QueryInterface返回E_NOINTERFACE,虽然看起来好像是不支持所查询的接口,实际上是因为COM没有办法将接口从一个套间列集到另外一个套间里面去。
COM里面,套间是一个 想象中的边界,用来在多线程环境中安全使用线程安全和线程不安全的COM对象。什么叫做线程安全的COM对象呢?再多线程环境中,如果这个COM对象自己实现了同步机制,可以被多个线程同时调用而不破坏对象内部数据的完整性的话,那么这个对象就叫做线程安全的对象。然而COM对象有一个目标就是,即使在多线程环境里面也可以安全地使用线程不安全的COM对象。也就是说,即使COM对象内部没有实现同步机制,COM也有一个机制可以创建一个线程安全的环境来使用这个对象,这个机制就是套间。在多线程环境里面,套间为线程不安全的COM对象创建了一个同步机制,COM保证在任意时刻都只有一个客户端在调用线程不安全的COM对象(调用它的函数—COM世界里面只有函数和接口)。
关于套间的知识,可以参考下面两篇文章:
http://www.codeguru.com/cpp/com-tech/activex/apts/article.php/c5529
http://www.codeguru.com/cpp/com-tech/activex/apts/article.php/c5533
而如果需要跨套间调用COM对象,这个函数调用,调用使用的参数和函数调用返回值都需要在套间之间被列集。而如果你的参数里面使用到了COM接口的话,例如跨套间使用一个COM对象,并且调用这个对象的QueryInterface方法,QueryInterface返回的接口就需要被列集。COM库使用CoMarshalInterThreadInterfaceInStream 和CoGetInterfaceAndReleaseStream来列集接口。
CoMarshalInterThreadInterfaceInStream 查询注册表HKEY_CLASSES_ROOT"Interface"{IID}"ProxyStubClsid32中要列集的接口是否注册有列集程序(Proxy和Stub程序)。
1.       如果这个键值存在,CoMarshalInterThreadInterfaceInStream会激活里面CLSID对应的COM对象来完成接口的列集;
2.       如果没有这个键值,那么说明没有提供方法列集接口,因此QueryInterface返回E_NOINTERFACE。
如果你细心一点的话,会发现很多接口的ProxyStubClsid32里面的CLSID是一样的,而且这些接口通常都会有另外一个子键:TypeLib。这是因为手工编写处理接口列集的COM对象的工作繁琐又容易出错,
1.       所以对于一些Dual接口,COM库(实际上是OLEAUT32.dll)提供了一个通用的类来列集所有的Dual接口,它所需要的就是类型库文件—因为类型库里面包含了所有COM对象的元数据(Meta Data);
2.       另外,对于非Dual接口,你也可以使用MIDL根据IDL文件生成对应的列集接口的COM对象。
这是在上一篇文章里面例子程序里面出现InvalidCastException的原因。
由于所有的COM对象都会被分配到一个相应的套间里面,因此在.NET里面,为了方便.NET程序调用COM对象,每一个.NET线程都会被分配到一个套间里面――即使你没有在代码里面指定线程运行的套间。在.NET线程里面创建的COM对象都会被分配到特定的套间里面,如果两个.NET线程 被分配到了不同的套间里,那么两个线程之间互相调用COM对象就需要列集函数调用。
在.NET 2.0以后,默认情况下.NET的线程是运行在多套间(MTA)里面的,但是在Visual Studio里面创建C#工程的时候,Visual Studio的项目模板会在你代码的Main函数上加上[STAThread]属性,表示主线程运行在STA套间里面,这样就会造成.NET程序有线程运行在两个不同的套间里面:
[STAThread]
public static void Main()
在Main函数上面加上[STAThread]属性会使.NET将主线程放在STA套间里面,而加上[MTAThread]属性则会将主线程放在MTA套间里面。
而对于其他线程,则需要使用Thread.SetApartmentState()函数来设置线程所运行的套间,而这个函数必须在线程启动之前调用,也就是在Thread.Start ()之前调用,这也是为什么.NET提供一个[STAThread]属性和[MTAThread]属性的原因――因为你没有办法在主线程启动之前设置主线程运行的套间。在线程里面,你可以使用Thread.GetApartmentState()函数来获取线程所运行的套间信息。
在COM套间对.NET程序使用COM对象的影响(上)文章里面的代码的修复方案如下,即将主线程的STAThread属性去掉,让大家都运行在程序里面唯一一个MTA套间里面,这样就没有列集接口的问题了:
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Runtime.InteropServices;
5. using System.Text;
6. using System.Diagnostics;
7. using System.Security.Cryptography;
8. using System.Security.Principal;
9. using Microsoft.Win32.SafeHandles;
10. using System.ComponentModel;
11. using System.Reflection;
12. using System.Security;
13. using System.IO;
14. using System.Threading;
15. using System.Security.Permissions;
16.
17. using ApartmentComponentLib;
18.
19. namespace CSharpQuestions
20. {
21.     public class Watcher
22.     {
23.         private object m_IStaObject = null;
24.
25.         public static void Main()
26.         {
27.             Console.WriteLine(Thread.CurrentThread.GetApartmentState());
28.             Watcher watcher = new Watcher();
29.             watcher.Initialize();
30.             watcher.CreateThreads().Join();
31.
32.             Console.WriteLine("Press any key");
33.             Console.ReadLine();
34.         }
35.
36.         private Thread CreateThreads()
37.         {
38.             Thread thread = new Thread(ThreadFunc);
39.             thread.Start();
40.
41.             return thread;
42.         }
43.
44.         private void ThreadFunc()
45.         {
46.             Console.WriteLine(Thread.CurrentThread.GetApartmentState());
47.             IStaObject2 obj = (IStaObject2)m_IStaObject;
48.             obj.TestMethod();
49.         }
50.
51.         private void Initialize()
52.         {
53.             m_IStaObject = new StaObject2Class();
54.         }
55.     }
56. }
COM套间对.NET程序使用COM对象的影响 SOX法案对IT控制的影响_『胡言乱语』BBS.KKIO.COM 】对如何看待中学生使用手机的情况研究 - 教育档客 jydoc.com COM编程入门—什么是COM,如何使用COM COM+ 未能加载文件或程序集 system.web.extensions解决方法 - 常见问题&帮助 - .Net源码论坛 ASP.net|论坛 - bbs.51aspx.com 美国思想库对国家政策的影响 http://www.hnass.com.cn/Index/0C/Index.htm www.880820.com 影响世界的100个管理定律 让PowerPoint中对象闪烁的方法两则 -- PPT无忧 www.ppt5u.com Oracle exp/imp导出导入工具的使用 - xxlinux.com 使用.NET FileSystemWatcher对象监控C#目录改变 - 开发者在线 - ... .net与java 使用自定义对象通过WebService调用 即时战略游戏中如何协调对象移动 - GameRes.com wiki wiki 技术探讨 原创的现买现卖-程序人生-donews.com 国际:为什么每个程序员都要学C语言的五个理由 , 语言,编程,汇编,面向对象,运行,学习,使用,程序,情况, , C#控件命名规范 asp.net 2.0 www.NorKoo.com ASP.NET开发环境搭建浅析 - 51CTO.COM .net知识和学习方法系列类型,对象,堆栈和托管堆 - 片言程序 - 博客园 【地震特辑】为什么说核泄漏对我国暂无影响? - 自然控 - 果壳网 guokr.com ADO.NET对象模型 .net打包 - Asp.net源码交流论坛 |-bbs.51aspx.com 预测:移动发展将对小企业所产生的4个影响 - CSDN.NET - CSDN资讯 《使用燃料对环境的影响》说课 长期使用电脑对身体健康的不良影响