日本国产亚洲-日本国产一区-日本国产一区二区三区-日本韩国欧美一区-日本韩国欧美在线-日本韩国欧美在线观看

當前位置:雨林木風下載站 > 技術開發教程 > 詳細頁面

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

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

更新時間:2022-04-28 文章作者:未知 信息來源:網絡 閱讀次數:

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的書,網上的資料也很少,只好自己摸索。期間曾在網上發貼提問,總是沒人回答,痛苦啊!

溫馨提示:喜歡本站的話,請收藏一下本站!

本類教程下載

系統下載排行

主站蜘蛛池模板: 91精品福利一区二区 | 狠狠操精品视频 | 日韩视频第一页 | 国产三级观看 | 欧美专区在线 | 中文字幕一区二区在线观看 | 欧美一级成人免费大片 | 国产精选在线观看 | 亚洲精品手机在线观看 | 天天干天天操天天爽 | 免费精品一区二区三区在线观看 | 欧美日韩一区二区三区在线播放 | 亚洲福利三区 | 97久久精品人人澡人人爽 | 中文字幕一宅男撸撸666 | 色婷婷综合在线视频最新 | 6一10周岁毛片在线 717影院理论午夜伦不卡久久 | chinese老太交视频在线观看 | 欧美日韩国产成人综合在线影院 | 国产日本亚洲欧美 | 国内永久第一免费福利视频 | 日本中文字幕不卡 | 青青草在线免费 | 久久久夜夜夜 | 亚洲欧洲视频在线观看 | 久久亚洲精品国产亚洲老地址 | 久久久久久综合一区中文字幕 | 国产精品久久毛片 | 亚洲欧美高清在线 | 天天玩夜夜操 | 亚洲精品系列 | 网曝门精品国产事件在线观看 | 亚洲一区二区三区在线 | 人与牲动交xxxxbbbb | 亚洲国产成人久久综合一区77 | 免费国产成人α片 | 国产乡下三级全黄三级带 | 天天爱天天做天天爽 | 国产成人毛片亚洲精品不卡 | 福利片在线观看免费高清视频 | 亚洲精品网站在线观看不卡无广告 |