成長(zhǎng)空間

          MiLife

          常用鏈接

          統(tǒng)計(jì)

          積分與排名

          Automation Testing Development

          最新評(píng)論

          COM與.NET的交互

           
           Advanced COM Interop


          .NET framework 是從COM的一種自然地進(jìn)步,因?yàn)檫@兩個(gè)模型共享了許多中心的主題,包括組件重用和語(yǔ)言中立。為了支持向后兼容,COM interop提供了不需要修改現(xiàn)有組件而能訪問(wèn)現(xiàn)有COM組件的方法。可以通過(guò)使用COM interop工具導(dǎo)入相關(guān)的COM類型來(lái)合并COM組件到.NET Framework的應(yīng)用中。一旦導(dǎo)入,COM的類型就可以使用了。

          COM interop 同時(shí)也提供了向前兼容使得COM的客戶可以像訪問(wèn)其他的COM對(duì)象一樣訪問(wèn)托管的代碼,COM interop又一次的提供了所謂的無(wú)縫從程序集中導(dǎo)出元數(shù)據(jù)(metadata)到類型庫(kù)并且像傳統(tǒng)COM組件一樣注冊(cè)托管組件的方法。無(wú)論是導(dǎo)出還是導(dǎo)入工具處理的結(jié)果都與COM規(guī)范一致。在運(yùn)行時(shí),如果需要的話common language runtime在COM對(duì)象和托管代碼之間列集(marshals)數(shù)據(jù)


          1. COM Wrappers
          COM在以下幾個(gè)方面與.NET Framework的對(duì)象模型有所不同:
          • COM對(duì)象的客戶程序必須管理這些對(duì)象的生命期;在.NET Framework 中CLR管理這些對(duì)象的生命期
          • COM的客戶通過(guò)請(qǐng)求一個(gè)接口并得到接口的指針來(lái)查詢一個(gè)服務(wù)是否有效,.NET的客戶可以通過(guò)反射(reflection)來(lái)得到一個(gè)對(duì)象的功能的描述
          • .NET的對(duì)象駐留在.NET Framework執(zhí)行環(huán)境管理的內(nèi)存中,執(zhí)行環(huán)境可以因?yàn)樾阅艿脑騽h除內(nèi)存中的對(duì)象并且更新它刪除的對(duì)象的所有引用。非托管的客戶,得到一個(gè)對(duì)象的指針,依賴于對(duì)象保留在相同的位置。這種客戶沒(méi)有那種處理在內(nèi)存中不在固定位置的對(duì)象的機(jī)制。


          為了克服這些不同,runtime提供了包裝類使得托管代碼和非托管代碼的客戶都認(rèn)為他們?cè)谧约旱沫h(huán)境中調(diào)用對(duì)象的方法。當(dāng)托管客戶調(diào)用一個(gè)COM對(duì)象的方法時(shí),runtime創(chuàng)建一個(gè)runtime callable wrapper (RCW)。RCWs抽象了托管代碼和非托管代碼引用機(jī)制的不同。Runtime還創(chuàng)建了一個(gè)COM callable wrapper (CCW)來(lái)實(shí)現(xiàn)其逆過(guò)程,使得COM的客戶能夠無(wú)縫的調(diào)用.NET對(duì)象的方法。如下圖所示


          COM wrapper overview



          在大多數(shù)情況下,標(biāo)準(zhǔn)的RCW或者CCW由runtime生成,為跨越COM和.NET Framework的邊界調(diào)用提供了足夠的列集。使用自定義的屬性,可以隨意的調(diào)整runtime表示托管代碼和非托管代碼的方式

          Runtime Callable Wrapper


          CLR(common language runtime)通過(guò)一個(gè)叫做runtime callable wrapper (RCW)的代理(proxy)暴露COM 對(duì)象. 雖然RCW對(duì)于.NET的客戶似乎是一個(gè)普通的.NET對(duì)象,但是它的主要功能卻是在.NET客戶和COM對(duì)象之間列集調(diào)用。


          運(yùn)行時(shí)正確的為每個(gè)COM對(duì)象創(chuàng)建一個(gè)RCW,而不管那個(gè)對(duì)象上存在的引用的個(gè)數(shù)。如下圖所示:任意數(shù)量的托管客戶可以保持一個(gè)暴露INew 和INewer 接口的COM對(duì)象的引用。運(yùn)行時(shí)為每個(gè)COM對(duì)象維護(hù)一個(gè)單獨(dú)的RCW。


          Accessing COM objects through the runtime callable wrapper



          使用來(lái)源于類型庫(kù)(type library)的元數(shù)據(jù)(metadata),運(yùn)行庫(kù)(runtime)創(chuàng)建將被調(diào)用COM對(duì)象和這個(gè)對(duì)象的包裝(wrapper)。每個(gè)RCW維護(hù)它所包裝的COM對(duì)象的接口指針并且在RCW不再需要時(shí)釋放COM對(duì)象。運(yùn)行庫(kù)(runtime)在RCW之上作垃圾收集。


          在其他的活動(dòng)中,RCW代表所包裝的對(duì)象在托管和非托管代碼之間列集(marshals)數(shù)據(jù)。特別的是,RCW在客戶和服務(wù)有不同的數(shù)據(jù)表現(xiàn)形式,并需要在他們之間傳遞數(shù)據(jù)時(shí),提供方法參數(shù)和方法返回值得列集。


          標(biāo)準(zhǔn)的wrapper執(zhí)行內(nèi)置(built-in)的列集規(guī)則。例如:當(dāng).NET的客戶傳遞一個(gè)String類型作為參數(shù)的一部分給托管對(duì)象的時(shí)候,wrapper把string類型那個(gè)轉(zhuǎn)化為BSTR類型。當(dāng)COM對(duì)象返回一個(gè)BSTR給托管的調(diào)用著的時(shí)候,調(diào)用著收到一個(gè)string。無(wú)論是客戶還是服務(wù)端接受和發(fā)送數(shù)據(jù)都使用自己熟悉的類型。還有一些其他的類型不需要變化,例如:一個(gè)標(biāo)準(zhǔn)的wrapper將在托管和非托管代碼之間一直傳遞4-byte integer而不做任何變化。


          RCW的主要目標(biāo)就是隱藏托管代碼模型和非托管代碼模型之間的差別,實(shí)現(xiàn)無(wú)縫的傳輸。RCW使用選擇的接口而不把它們暴露給.NET的客戶端,如下圖所示


          COM interfaces and the runtime callable wrapper



          當(dāng)創(chuàng)建一個(gè)早期的COM對(duì)象的時(shí)候,RCW是一個(gè)特殊的類型。它實(shí)現(xiàn)了COM對(duì)象實(shí)現(xiàn)的一些接口并且暴露它們的方法,屬性和時(shí)間。如圖所示:RCW暴露了INew接口,但是使用了IUnknown 和IDispatch接口。RCW向.NET客戶暴露了INew的所有成員


          COM Callable Wrapper


          當(dāng)COM的客戶調(diào)用.NET的對(duì)象時(shí),CLR創(chuàng)建這個(gè)托管的對(duì)象和這個(gè)托管的對(duì)象COM callable wrapper(CCW),COM客戶可以使用CCW作為托管對(duì)象的一個(gè)代理,而不能夠直接使用.NET的對(duì)象。

           
          Runtime正確的創(chuàng)建托管對(duì)象的CCW,不管要求這個(gè)服務(wù)的COM客戶的數(shù)量。如下圖所示,多個(gè)COM客戶可以保持包含INew 接口的CCW的引用,CCW,反過(guò)來(lái)包含一個(gè)實(shí)現(xiàn)了接口和垃圾收集的托管對(duì)象的單獨(dú)的引用。COM和.NET的客戶可以同時(shí)在相同的托管對(duì)象是發(fā)起調(diào)用。


          Accessing .NET objects through COM callable wrapper


          COM callable wrappers對(duì)于運(yùn)行在.NET Framework上的其他類是不可見(jiàn)的。它們的主要目的是在托管和非托管代碼之間列集(marshal)調(diào)用,而且CCWs同時(shí)也管理著它所包含對(duì)象的identity和對(duì)象的生存期(lifetime)


          Object Identity
          運(yùn)行時(shí)(runtime)在它的能夠垃圾回收的堆里(garbage-collected heap)為.NET對(duì)象分配內(nèi)存,這能使runtime需要的時(shí)候在內(nèi)存中移動(dòng)對(duì)象。相反,runtime在不能進(jìn)行垃圾收集的堆上為CCW分配內(nèi)存,使COM客戶能夠直接引用它。


          Object Lifetime
          與它所包含的.NET 對(duì)象不同,CCW是一個(gè)基于引用計(jì)數(shù)的傳統(tǒng)的COM。當(dāng)CCW的引用計(jì)數(shù)減少到0,wrapper釋放自己在托管對(duì)象上的引用。沒(méi)有引用的托管的對(duì)象在下一次垃圾收集的周期內(nèi)將被收集。


          Customizing Standard Wrappers
          這部分將描述如何自定義標(biāo)準(zhǔn)的runtime callable wrappers(RCW) and COM callable wrappers.(CCW)


          ? Runtime Callable Wrappers
          當(dāng).NET激活一個(gè)COM對(duì)象時(shí),運(yùn)行時(shí)(runtime)生成一個(gè)包含COM類型的runtime callable wrapper (RCW),如下圖所示, 運(yùn)行時(shí)(runtime)使用從導(dǎo)入的類型庫(kù)而得到的元數(shù)據(jù)(metadata)來(lái)生成RCW。Wrapper根據(jù)interop marshaling service定義的規(guī)則列集數(shù)據(jù)。


          RCW generation and method calls




          有兩個(gè)方式可以自定義RCW。如果你可以修改Interface Definition Language (IDL)的源文件,你可以給type library file (TLB) 添加屬性然后導(dǎo)入TLB。還可以應(yīng)用interop-specific attributes來(lái)導(dǎo)入類型生成新的程序集(assembly)支持自定義的標(biāo)準(zhǔn)RCWs被這些attributes限制


          To modify the IDL source

          1. Apply TLB attributes to libraries, types, members, and parameters. Use the custom keyword and an attribute value to change metadata. By applying TLB attributes, you can:
          • Specify the managed name of an imported COM type, instead of allowing the import utility to select the name according to standard conversion rules.
          • Explicitly define a destination namespace for the types in a COM library.
          2. Compile the IDL source code.
          3. Generate an assembly from the resulting type library file or from a dynamic link library file (DLL) that contains the type you intend to implement.


          To modify an imported assembly
          1. Import the type library file. Use the Type Library Importer (Tlbimp.exe) to generate an assembly DLL.
          2. Create a text file from the imported assembly by using the MSIL Disassembler (Ildasm.exe).
          3. Apply interop attributes to the text file.
          4. Generate a new assembly from the modified text file by using the MSIL Assembler (Ilasm.exe).


          ? COM Callable Wrappers
          COM callable wrapper (CCW) 向COM導(dǎo)出NET Framework對(duì)象. 通過(guò)把一個(gè)托管的工程編譯成一個(gè)DLL的程序集, 可以自動(dòng)的創(chuàng)建需要的元數(shù)據(jù)來(lái)描述程序集中的類型. 當(dāng)一個(gè)COM的客戶激活托管對(duì)象時(shí),運(yùn)行時(shí)使用元數(shù)據(jù)來(lái)生成CCW.
          自定義CCW,你的托管的代碼要遵循交互規(guī)范屬性(interop-specific attributes),并且把編譯代碼編譯成程序集,如下圖所示,在這個(gè)例子中,Tlbexp.exe把托管類型編譯為COM


          CCW 的生成和方法調(diào)用



          通過(guò)在代碼中添加屬性,可以在交互列集服務(wù)限定的范圍內(nèi)改變接口和數(shù)據(jù)的列集行為。例如,可以你可以控制方法參數(shù)傳遞的格式,也可以控制程序集中的什么類型暴露給COM


          2 .托管和非托管的線程(Managed and Unmanaged Threading)


          COM組件使用套間(apartments)來(lái)同步資源的訪問(wèn)。與之對(duì)應(yīng)的是,托管對(duì)象使用同步區(qū)域(synchronized regions),同步原語(yǔ)例如互斥量(mutexes)鎖定和完成異步端口,同步上下文來(lái)保證所有的共享資源以線程安全的方式被使用。


          對(duì)于可交互性來(lái)說(shuō),CLR(common language runtime)在調(diào)用COM對(duì)象時(shí),創(chuàng)建并初始化一個(gè)套間,一個(gè)托管的線程可以創(chuàng)建并且進(jìn)入一個(gè)包含一個(gè)線程的single-threaded apartment (STA)或者包含多個(gè)線程的multi-threaded apartment (MTA)。當(dāng)COM的套間和線程的套間兼容的時(shí)候,COM允許調(diào)用的線程直接調(diào)用COM對(duì)象的方法,如果套間不兼容,COM創(chuàng)建兼容的套間并通過(guò)代理列集(marshals)所有的調(diào)用。

           
          在第一個(gè)對(duì)非托管代碼的調(diào)用時(shí),運(yùn)行時(shí)調(diào)用CoInitializeEx來(lái)初始化MTA或者STA的COM套間。可以使用System.Threading.ApartmentState屬性來(lái)控制創(chuàng)建的套間的類型是MTA, STA, 或者Unknown.在代理存根或者TLB已經(jīng)注冊(cè)后,不一定義定設(shè)定要設(shè)定這個(gè)屬性。
          下表列出了ApartmentState的枚舉值和對(duì)應(yīng)的COM套間初始化調(diào)用

          ApartmentState enumeration value
          COM apartment initialization
          MTA
          CoInitializeEx(NULL, COINIT_MULTITHREADED)
          STA
          CoIntializeEx(NULL, COINIT_APARTMENTTHREADED)
          Unknown
          CoInitializeEx(NULL, COINIT_MULTITHREADED)

          當(dāng)COM對(duì)象和托管線程在不兼容的套間時(shí),所有的調(diào)用都通過(guò)COM創(chuàng)建的代理,下面的代碼例子顯示了怎么在托管代碼重創(chuàng)建一個(gè)STA套間模型的COM對(duì)象AptSimple


          [C#]
          using System.Threading;
          using APTOBJLib;
          ...
          AptSimple obj = new AptSimple ();
          obj.Counter = 1;
          為了消除代理存根,顯著的提高性能,注意創(chuàng)建對(duì)象之前的ApartmentState
          [C#]
          using System.Threading;
          using APTOBJLib;
          ...
          Thread.CurrentThread.ApartmentState = ApartmentState.STA;
          AptSimple obj = new AptSimple ();
          obj.Counter = 1;
          設(shè)定套間狀態(tài)之后,可以向下面那樣通過(guò)程序查詢狀態(tài)
          [C#]
          Thread.CurrentThread.ApartmentState = ApartmentState.STA;
          if (Thread.CurrentThread.ApartmentState == ApartmentState.STA) {
          // All is OK.
          }
          else {
          // Incompatible apartment state.
          }

          3 托管和非托管的事件(Managed and Unmanaged Events)

          .NET Framework的事件模型與傳統(tǒng)的COM的事件模型不同。托管的事件模型基于委托(delegate),er非托管的事件(在COM中)基于連接點(diǎn)(connection points)。兩個(gè)模型都是緊耦合的事件系統(tǒng),因?yàn)榭蛻簦ㄊ录邮苷撸┖头?wù)(事件發(fā)送者)必須同時(shí)的運(yùn)行。

          這一部分描述了怎樣過(guò)渡托管和非托管的事件系統(tǒng),使得對(duì)象可以跨域交互邊界發(fā)送和接收事件。

          COM 事件
          這部分提供關(guān)于連接點(diǎn)的概要介紹以及用來(lái)說(shuō)明COM事件相關(guān)的通用術(shù)語(yǔ)
          連接點(diǎn)在客戶和COM的服務(wù)器之間確立了一種雙向de通信機(jī)制。通過(guò)這種機(jī)制,COM服務(wù)器在事件發(fā)生時(shí)可以回調(diào)客戶。例如,服務(wù)器(像Microsoft Internet Explorer)可以發(fā)出一個(gè)事件來(lái)向客戶程序報(bào)告一個(gè)變化(例如標(biāo)題變化)。客戶創(chuàng)建了一個(gè)叫做event sink內(nèi)部的COM對(duì)象來(lái)響應(yīng)通知,當(dāng)收到通知,客戶可以執(zhí)行事件相關(guān)的操作。


          event sink提供了向服務(wù)器暴露事件相關(guān)方法的接口。服務(wù)器通過(guò)這些事件相關(guān)的方法激發(fā)事件。客戶像實(shí)現(xiàn)普通的COM接口一樣實(shí)現(xiàn)event sink接口。服務(wù)器聲明這個(gè)接口為出接口,COM服務(wù)的作者在類型庫(kù)中隊(duì)這個(gè)接口應(yīng)用source 屬性。服務(wù)器使用event sink接口的定義來(lái)確定sink并且invoke方法


          實(shí)現(xiàn)了event sink接口的COM客戶通常叫做event sink,或者簡(jiǎn)單的稱為sink。
          在下圖中,sink實(shí)現(xiàn)了ISinkEvents接口,服務(wù)器可以激發(fā)事件
          連接點(diǎn)事件模型(Connection point event model)



          event sink的接口確定之后,sink必須與源對(duì)象建立連接,連接點(diǎn)的機(jī)制使用下面的過(guò)程連接sink和source:
          1. The sink queries a server object for the IConnectionPointContainer interface. If the object supports connection points, it returns a pointer.
          2. Using methods on the container object, the sink locates the IConnectionPoint interface representing a specific connection point. Since a server can support multiple outgoing interfaces, a client must match its sink to the interface identifier (IID) of a particular connection point interface.
          3. Having obtained the correct connection point object, the sink calls IConnectionPoint::Advise to register its sink interface pointer. The server (source) holds the connection (and raises events to it) until the client breaks the connection by calling IConnectionPoint::Unadvise.


          處理COM源發(fā)出的事件(Handling Events Raised by a COM Source)
          如果你不熟悉.NET Framework提供的基于委托(delegate-based)的事件模型,參考Handling and Raising Events。

          An imported delegate signature comprises the sink event interface, an underscore, the event name, and the word EventHandler: SinkEventInterface_EventNameEventHandler.
          .NET的客戶(event sink)可以接受現(xiàn)存的COM服務(wù)器(event source)的事件。COM interop在你的托管客戶的元數(shù)據(jù)中產(chǎn)生必要的委托。一個(gè)導(dǎo)入的委托簽名(signature)由sink event接口,一個(gè)下劃線,事件名稱和EventHandler組成


          與現(xiàn)存的COM事件源交互(To interoperate with an existing COM event source)
          1. Obtain the primary interop assembly for the COM server if the COM types are to be shared by other applications. A primary interop assembly contains metadata representing the converted type library and is signed by the publisher.
          Note If the primary interop assembly is not available or if the assembly is to be used privately, you can import the type library by using Tlbimp.exe or an equivalent API.
          The conversion process generates a delegate for each event; however, you only have to sink the events that interest you.
          2. You can use a metadata browser, such as Ildasm.exe, to identify events delegates.
          3. Consume events from the COM event source the same way you consume events from a managed event source.
          下面的例子說(shuō)明了怎樣打開(kāi)一個(gè)Internet Explorer窗口,得到Internet Explorer對(duì)象發(fā)出的事件并在托管的代碼中處理。Internet Explorer的類型(包括事件委托)的定義從被SHDocVw.dll導(dǎo)入為元數(shù)據(jù),例子激發(fā)TitleChange事件
           

          [C#]
          namespace InternetExplorer
          {
              using System;
              using System.Runtime.InteropServices;
              using SHDocVw;
           
              public class Explorer
              {
                  public static void Main()
                  {
                      Explorer explorer = new Explorer();
                      explorer.Run();
                  }
                  public void Run()
                  {
                      Object o = null;
                      String s;
           
                      try
                      {
                          // Starts the browser.
                          m_IExplorer = new SHDocVw.InternetExplorer();
                      }
                      catch(Exception e)
                      {
                          Console.WriteLine("Exception when creating Internet
                          Explorer object {0}", e);
                          return;
                      }
           
                      // Wires your event handlers to m_IExplorer.
                      SetAllEvents();
           
                      try
                      { 
                          // Goes to the home page.
                          m_WebBrowser = (IWebBrowserApp) m_IExplorer;
                          m_WebBrowser.Visible = true;
                          m_WebBrowser.GoHome();
           
                          // Starts navigating to different URLs.
                          Console.Write("Enter URL (or enter to quit): ");
                          s = Console.ReadLine();
                          while (s != "" && m_IExplorer != null &&
                              m_WebBrowser != null)
                          {
                              m_WebBrowser.Navigate(s, ref o, ref o, ref o,
                                    ref o);
                              Console.Write("Enter URL (or enter to quit): ");     
                              s = Console.ReadLine();
                          }
           
                          m_WebBrowser.Quit();
                      }
                      catch(Exception sE)
                      {
                          if (m_IExplorer == null && m_WebBrowser == null)
                          {
                              Console.WriteLine("Internet Explorer has gone away");
                          }
                          else
                          {
                              Console.WriteLine("Exception happens {0}", sE);
                          }
                      }
                  }
                  // Uses the += syntax for adding delegates to events.
                  void SetAllEvents()
                  {
                      if (m_IExplorer != null)
                      {
                          // Title Change event
                          // DWebBrowserEvents2 is the name of the sink event
                          //interface.
                          // TitleChange is the name of the event.
                          // DWebBrowserEvents2_TitleChangeEventHandler is the
                          // delegate name assigned by TlbImp.exe.
                          DWebBrowserEvents2_TitleChangeEventHandler
               DTitleChangeE = new DWebBrowserEvents2_TitleChangeEventHandler(OnTitleChange);
                        m_IExplorer.TitleChange += DTitleChangeE;
                      }
                  }
          ///////////////////////////////////////////////////////////////////////
                  // Define event handlers.
                  // Document title changed
                  static void OnTitleChange(String Text)
                  {
                      Console.WriteLine("Title changes to {0}", Text);
                  }
            
          //////////////////////////////////////////////////////////////////////////
                  // The following are class fields.
                  static private SHDocVw.InternetExplorer m_IExplorer = null;
                  static private IWebBrowserApp m_WebBrowser = null;
              }
          }

          posted on 2007-07-25 11:19 Picasso 閱讀(2456) 評(píng)論(0)  編輯  收藏


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 自治县| 额济纳旗| 修水县| 从江县| 卢龙县| 榆中县| 铁岭市| 安陆市| 甘孜县| 察隅县| 苗栗市| 泊头市| 崇文区| 镇巴县| 彰化市| 油尖旺区| 晋城| 岳普湖县| 鹤庆县| 重庆市| 襄樊市| 光山县| 禹州市| 嘉善县| 绿春县| 淮阳县| 和田市| 嘉定区| 甘德县| 三明市| 平昌县| 平凉市| 肇源县| 安溪县| 墨竹工卡县| 海盐县| 建德市| 吴江市| 富裕县| 扎赉特旗| 柘荣县|