六月婷婷综合激情-六月婷婷综合-六月婷婷在线观看-六月婷婷在线-亚洲黄色在线网站-亚洲黄色在线观看网站

明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

用C++Builder創建多線程COM服務器

[摘要]Sunspot Lee一、線程、Apartment和進程  說道COM的線程模型,大家就會想到各種Apartment模型。但Apartment究竟是什么?如何建立一個Apartment呢?  Apa...
Sunspot Lee

一、線程、Apartment和進程

  說道COM的線程模型,大家就會想到各種Apartment模型。但Apartment究竟是什么?如何建立一個Apartment呢?

  Apartment就是線程的容器,線程中有關COM的操作必須在Apartment中進行。Apartment分為STA和MTA兩種,STA是只能容納一個線程的容器,MTA是能容納多個線程的容器。COM規定,一個進程中可以有多個STA,但最多只能有一個MTA。線程調用CoInitializeEx(NULL,COINIT_APARTMENTTHREADED)后,這個線程就建立并且進入了一個STA,線程調用CoInitializeEx(NULL,COINIT_MULTITHREADED)后,這個線程就進入了進程公用MTA。一個線程不能同時進入兩個Apartment。線程調用CoUninitialize()后,這個線程就退出了它所在的Apartment。設計COM對象時設定的“Apartment模型”就是指這個COM對象可以呆在那種Apartment中。一個線程建立的COM對象自動地呆在這個線程所在的Apartment中。要是這個線程建立了很多個COM對象,那這些對象都呆在這個線程所在的Apartment中。

  一個線程可以直接訪問它所在的Apartment中的COM對象,但要訪問另一個Apartment中的COM對象就必須經過調度。因為STA中只有一個線程,別的線程要訪問這個線程建立的COM對象就必須讓這個線程代勞了,如此一來,對這個Apartment中所有的COM對象的訪問都是序列化的,這些COM對象就不用擔心有好幾個線程同時訪問它的麻煩事。MTA中的COM對象就沒這么舒服了,它們必須考慮到可能會有好幾個線程同時訪問它們。MTA之外的一個線程訪問MTA中的一個COM對象時,系統會從COM系統線程池中取出一個線程進入MTA,由它來代表客戶線程訪問這個COM對象。(COM系統線程池的機理是怎么樣的?池中有幾個線程?)



二、客戶與服務器

  COM對象位于服務器中,服務器分為進程內服務器、進程外服務器、遠程服務器三種。進程內服務器是一個DLL文件,進程外服務器是一個EXE文件,遠程服務器是另一臺計算機上的一個DLL文件或EXE文件。遠程服務器如果是一個DLL文件的話,由一個被稱為“Surrogate”的代理程序調用它。

  進程內服務器中的COM對象的Apartment模型如果與客戶線程所在的Apartment相配合的話,客戶線程建立COM對象時會直接建立在客戶線程所在的Apartment中。比如Apartment模型與STA、Free模型與MTA,Both模型與STA或MTA。這樣客戶線程就可以直接調用COM對象而不用調度。否則就會專門建立一個線程,然后由這個線程建立COM對象,COM對象和客戶線程就分處在兩個Apartment中。進程外服務器和遠程服務器中的COM對象一定不會建立在客戶線程所在的Apartment中。對它們的調用一定要經過調度的。



三、在C++Builder下建立一個多Apartment的進程外服務器

  由于不必考慮并行的問題,COM對象一般設成使用Apartment線程模型。進程內服務器還沒什么問題,如果你試著建了一個進程外服務器,并且讓幾個客戶同時訪問服務器中的對象的話,就會發現這些訪問不是同時進行的。如果有一個訪問特別費時間,它后面的訪問就要等很久才能進行。這是因為服務器中只有一個STA,雖然每個線程都建立了自己的COM對象,但這些對象都在這個STA中,當然無法并行執行。

  克服這個問題的辦法很簡單,打開Borland\CBuilder5\Include\Atl\Atlmod.h文件,把第266行的:

typedef TATLModule<CComModule> TComModule;

改成:

#ifdef __DLL__

typedef TATLModule<CComModule> TComModule;

#else

typedef TATLModule<CComAutoThreadModule<CComSimpleThreadAllocator> > TComModule;

#endif

再打開Borland\CBuilder5\Include\Atl\Atlcom.h文件,把第3214行的:

        DECLARE_CLASSFACTORY()

改成:

#ifdef __DLL__

        DECLARE_CLASSFACTORY()

#else

        DECLARE_CLASSFACTORY_AUTO_THREAD()

#endif

就可以了。重新編譯你的程序,同時開兩個客戶試一試,是不是并發執行了?

  先別高興得太早,如果你同時開了五個客戶,并且其中四個在執行費時的訪問,你就會發現第五個客戶的訪問要等待一段時間。這種現象與C++Builder的實現代碼有關。

  作了前面的修改后,服務器啟動后會預先生成幾個線程,這些線程各自進入一個STA中。當服務器接到客戶的訪問要求后,會循環指定一個線程負責這個客戶的建立COM對象、訪問COM對象的事務。

    比如第一個客戶要求建立一個COM對象,服務器就給一號線程發消息,讓這個線程建立一個COM對象并把這個COM對象的接口傳給客戶,以后第一個客戶對這個COM對象的訪問就全由一號線程代理。而第二個客戶的建立COM對象、訪問COM對象的事務就由服務器指定二號線程來辦,如果客戶太多,線程用完了,服務器又會讓一號線程負責客戶的要求,依次循環。如果客戶很多,線程可能會負責幾個客戶的訪問要求,而由同一個線程服務的客戶的訪問就會順序執行。預先生成的線程數缺省為系統的CPU個數乘以四,也就是四個(除非你的機器有好幾個CPU)。

  只能同時服務四個客戶當然是不行的,讓我們繼續修改。打開主CPP文件,可以看到下面兩行代碼:

TComModule ProjectModule(0);

TComModule &_Module = ProjectModule;

改為:

TComModule ProjectModule(MyInitATLServer);

TComModule &_Module = ProjectModule;

其中“MyInitATLServer”是一個新加的函數,定義如下:

void __fastcall MyInitATLServer()

{

  if (_Module.SaveInitProc)

    _Module.SaveInitProc();



  _Module.Init(ObjectMap, Sysinit::HInstance, NULL, 6);//注意這個6

  _Module.m_ThreadID = ::GetCurrentThreadId();

  _Module.m_bAutomationServer = true;

  _Module.DoFileAndObjectRegistration();

  AddTerminateProc(_Module.AutomationTerminateProc);

}

看到那個6沒有,這代表服務器啟動后會預先生成6個線程,也就能同時服務6個客戶。這個6可以改成別的數,當然不要太大了,不然機器垮了可別怪我。

  改到現在你可能比較滿意了,但其實這個服務器還是有缺陷:一開始就生成所有線程是不是太浪費了?循環分配線程好象也不太合理,更重要的是,如果客戶程序中途垮了,沒有Release它建立的COM對象,那這個COM對象將一直存在下去,占用的資源無法收回。

  要解決這些問題就比較麻煩了,建議大家看一看ATL源代碼,編寫自己的TComModule類和CComThreadAllocator類。



四、編寫多線程客戶程序時要注意的問題

  建立客戶程序時必須包含的*_ATL.h文件中有一個很好的COM對象包裝類。比如我建立了一個ComLib服務器,里面有一個MyComObj對象,那么在ComLib_ATL.h文件中有一個TCOMIMyComObj類,它很好的封裝了MyComObj對象。寫單線程程序時可以這樣建立它:

TCOMIMyComObj aComObj = CoMyComObj::CreateInstance();

(CoMyComObj是定義在在ComLib_ATL.h文件中的一個輔助類)然后就可以使用aComObj了,不必調用CoInitializeEx()和CoUninitialize(),也不必釋放aComObj。假設MyComObj對象中定義了一個方法fun(),一個屬性num,可以這樣使用:

  aComObj.fun();

  aComObj.num = 14;

  int val = aComObj.num;

注意到num的訪問方法了嗎?C++Builder靈活運用了特有的__property關鍵字,不必調用get_num()和set_num()了。

  如果在寫多線程客戶程序時也這樣就會出問題:除了第一個線程正常外,后面的的線程無法建立COM對象了。

  問題出在CoMyComObj里面,它保證了會調用CoInitializeEx()和CoUninitialize()并且在整個進程中只會調用一次。而在多線程客戶程序中,每個線程都必須調用CoInitializeEx()和CoUninitialize()一次。因此,除了第一個線程成功進入了Apartment,別的線程都失敗了。

  可以這樣建立TCOMIMyComObj對象:

    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    IMyComObj *pComObj;

    OleCheck(CoCreateInstance(CLSID_MyComObj, NULL, CLSCTX_LOCAL_SERVER

      , IID_IMyComObj, (void **)(&pComObj)));

    TCOMIComObjInExe aComObj(pComObj);

  ……使用aComObj……

  CoUninitialize();

注意,這段代碼必須寫在TThread::Execute()中,因為只有TThread::Execute()里的代碼才是真正運行在新線程中的。另外決不能調用pComObj->Release()。



后記

  學COM的念頭起于看李維寫的那三本書中的第一本的時候,李維描述了建立多線程服務器的重要性,但具體方法只是一筆帶過。后來我看了Delphi帶的例子,想用在C++Builder中,卻無從下手。在關于COM的部分,Delphi和C++Builder相差太大了,而又沒有這方面的C++Builder的書,網上的資料也很少,只好自己摸索。期間曾在網上發貼提問,總是沒人回答,痛苦啊!


主站蜘蛛池模板: 亚洲精品老司机福利在线播放 | 四虎影院在线看 | 午夜影院免费观看 | 五月天久久久 | 亚洲国产日韩欧美在线a乱码 | 天天看逼| 性性欧美| 武则天a级片 | 四虎影院官网 | 天天看天天色 | 一级成人a做片免费 | 欧美一级特黄aaaaaa在线看片 | 欧美一区二区三区东南亚 | 亚洲成人午夜影院 | 欧美在线91| 日本一区二区高清免费不卡 | 色综合一区二区三区 | 日韩a级片| 涩涩涩涩涩 | 天天影视色香欲综合网天天录日日录 | 日本片网址| 日本午夜激情 | 日本aⅴ在线 | 全部毛片免费看 | 日韩免费精品一级毛片 | 青青国产成人久久激情91麻豆 | 性亚洲无删减 | 伊人婷婷色香五月综合缴激情 | 亚洲免费成人网 | 亚洲国产成人久久77 | 天天色综合3 | 日韩欧美精品中文字幕 | 日韩精品一区二区三区不卡 | 青草草产国视频 | 人人干人人看 | 在线播放性xxx欧美 在线播放色 | 亚洲欧美日韩高清一区二区三区 | 亚洲成a人不卡在线观看 | 一级做a爰片性色毛片思念网 | 日日摸夜夜添夜夜添成人 | 色综合久久六月婷婷中文字幕 |