COM套间对.NET程序使用COM对象的影响(上)
在COM时代里,套间是用来简化多线程环境下使用COM对象的,然而在.NET里面,微软又放弃了套间的概念,这样给我们在.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没有办法将接口从一个套间列集到另外一个套间里面去。
具体的原因在后续文章里面解释。
版权声明
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!