国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > .NET > 正文

.Net平臺(tái)下CLR程序載入原理分析

2024-07-10 13:02:14
字體:
供稿:網(wǎng)友
flier lu <[email protected]>
  
注意:本系列文章在水木清華bbs(smth.org)之.net版首發(fā),
     轉(zhuǎn)載請(qǐng)保留以上信息,發(fā)表請(qǐng)與作者聯(lián)系
  
  與傳統(tǒng)的win32可執(zhí)行程序中的本機(jī)代碼(native code)不同,
微軟推出的.net架構(gòu)中,可執(zhí)行程序的代碼是以類似java byte code的
il (intermediate language)偽代碼形式存在的。在.net可執(zhí)行程序載入后,
il代碼由clr (common language runtime)從可執(zhí)行文件中取出,
交由jit (just-in-time)編譯器,根據(jù)相應(yīng)的元數(shù)據(jù)(metadata),
實(shí)時(shí)編譯成本機(jī)代碼后執(zhí)行。
  因此,一個(gè)clr可執(zhí)行程序的啟動(dòng)過程可以分為三個(gè)步驟。
  首先,windows的可執(zhí)行程序載入器(os loader)載入
pe (portable executable)結(jié)構(gòu)的可執(zhí)行文件映像(pe image),
將執(zhí)行權(quán)傳遞給clr的支持庫中的unmanaged code。
  其次,啟動(dòng)或使用現(xiàn)有的clr引擎,建立新的應(yīng)用域(application domain),
將配件(assembly)載入到此應(yīng)用域中。
  最后,將執(zhí)行權(quán)從unmanaged code傳遞給managed code,執(zhí)行配件的代碼。
  下面我將詳細(xì)說明以上步驟。
  
  自從win95發(fā)布以來,可執(zhí)行程序的pe結(jié)構(gòu)就沒有發(fā)生大的改動(dòng)。
此次.net平臺(tái)發(fā)布,也只是利用了pe結(jié)構(gòu)中現(xiàn)有的預(yù)留空間,
以保持pe結(jié)構(gòu)的穩(wěn)定,最大程度保持向后兼容。
(詳情請(qǐng)參看筆者《ms.net平臺(tái)下clr 擴(kuò)展pe結(jié)構(gòu)分析》一文)
  clr程序在編譯后,將可執(zhí)行程序入口直接以一個(gè)間接跳轉(zhuǎn)指令
指向mscoree.lib中的_corexemain函數(shù)(dll將入口指向_cordllmain函數(shù))。
因此clr可執(zhí)行程序在被os loader載入后,將由_corexemain函數(shù)處理clr引擎
啟動(dòng)事宜。此函數(shù)將啟動(dòng)或使用一個(gè)現(xiàn)有的clr host來加載il代碼。
  常見的clr host有asp.net、ie、shell、數(shù)據(jù)庫引擎等等,
他們的作用是啟動(dòng)一個(gè)clr實(shí)例,管理在此clr實(shí)例中運(yùn)行的clr程序。
  
  我們接著來看一看一個(gè)clr host是如何實(shí)際運(yùn)作的。
  clr作為一個(gè)引擎,在同一臺(tái)計(jì)算機(jī)上是可以存在多個(gè)版本的,
不同版本之間可以通過配置良好共存。在
%windir%/microsoft.net/framework
(%windir%表示windows系統(tǒng)目錄所在位置)目錄下,
我們可以看到以版本號(hào)為目錄名的多個(gè)clr版本,
如%windir%/microsoft.net/framework/v1.0.3705等等,
也可以在注冊(cè)表的
hkey_local_machine/software/microsoft/.netframework/policy/v1.0
鍵下查看詳細(xì)的版本兼容性.name是build號(hào),value是兼容的build號(hào).
而每一個(gè)clr版本又分為server和workstation兩類運(yùn)行庫,
我們等會(huì)講創(chuàng)建clr時(shí)會(huì)詳細(xì)談到.
  clr host在啟動(dòng)clr之前,必須通過一個(gè)startup shim的庫進(jìn)行操作,
實(shí)際上就是mscoree.dll,他提供了版本無關(guān)的操作函數(shù),以及啟動(dòng)clr所需
的支持,如corbindtoruntimeex函數(shù).
  clr host通過shim的支持庫,將clr引擎載入到進(jìn)程中.具體函數(shù)如下
stdapi corbindtoruntimeex(lpcwstr pwszversion,
  lpcwstr pwszbuildflavor, dword startupflags,
  refclsid rclsid, refiid riid, lpvoid far *ppv);
  參數(shù)pwszversion指定要載入的clr版本號(hào),注意必須在前面帶一個(gè)小寫的"v",
如"v1.0.3705",可以通過查閱前面提到的注冊(cè)表鍵,獲取當(dāng)前系統(tǒng)安裝的不同clr
版本情況,或指定固定的clr版本.也可以傳遞null給這個(gè)參數(shù),系統(tǒng)將自動(dòng)選擇最新
版本的clr載入.
  參數(shù)pwszbuildflavor則指定載入的clr類型,"srv"和"wks".
前者適用于多處理器的計(jì)算機(jī),能夠利用多cpu提高并行性能.對(duì)單cpu
系統(tǒng)而言,無論指定哪種類型都會(huì)載入"wks",傳遞null也是如此.
  參數(shù)startupflags是一個(gè)組合參數(shù).由多個(gè)標(biāo)志位組成.
  startup_concurrent_gc標(biāo)志指定是否使用并發(fā)的gc(garbage collection)
機(jī)制,使用并發(fā)gc能夠提高系統(tǒng)的用戶界面相應(yīng)效率,適合窗口界面使用較多的程序.
但并發(fā)gc會(huì)因?yàn)闊o謂的線程上下文(thread context)切換損失效率.
  以下三個(gè)參數(shù)用于指定配件載入優(yōu)化策略.我們等會(huì)詳細(xì)討論.
  startup_loader_optimization_single_domain = 0x1 << 1,
  startup_loader_optimization_multi_domain  = 0x2 << 1,
  startup_loader_optimization_multi_domain_host = 0x3 << 1,
  接著的三個(gè)參數(shù)用于獲取icorruntimehost接口.
  實(shí)際調(diào)用實(shí)例如下.
ccomptr<icorruntimehost> sphost;
check(corbindtoruntimeex(null, l"wks",
  startup_loader_optimization_single_domain | startup_concurrent_gc,
  clsid_corruntimehost, iid_icorruntimehost, (void **)&sphost));
  這行代碼載入最高版本clr的wks類型運(yùn)行庫,為單應(yīng)用域進(jìn)行優(yōu)化并使用并發(fā)gc機(jī)制.
  前面提到了配件載入優(yōu)化策略,要理解這個(gè)概念,我們必須先了解應(yīng)用域的概念.
傳統(tǒng)win程序中,資源的分配管理單位是進(jìn)程,操作系統(tǒng)以進(jìn)程邊界將應(yīng)用程序?qū)嵗綦x開
,
單個(gè)進(jìn)程的崩潰不會(huì)對(duì)其他進(jìn)程產(chǎn)生直接影響,進(jìn)程也不能直接使用其他進(jìn)程的資源.
進(jìn)程很好,但使用進(jìn)程的代價(jià)太大,為此win32引入了線程的概念.同一進(jìn)程中的線程能夠
共享資源,線程管理和切換的代價(jià)也遠(yuǎn)遠(yuǎn)小于進(jìn)程.但因?yàn)樵谕贿M(jìn)程中,線程的崩潰會(huì)直

影響到其他線程的運(yùn)行,也無法約束線程間數(shù)據(jù)的直接訪問等等.
  為此,clr中引入了application domain應(yīng)用域的概念.應(yīng)用域是介于進(jìn)程和線程
之間的一種邏輯上的概念.他既有線程輕巧,管理切換快捷的優(yōu)點(diǎn),也有進(jìn)程在穩(wěn)定性方面
的優(yōu)點(diǎn),單個(gè)應(yīng)用域的崩潰不會(huì)直接影響到同一進(jìn)程中的其他應(yīng)用域,應(yīng)用域也無法直接
訪問同一進(jìn)程中的其他應(yīng)用域的資源,這方面和進(jìn)程完全相同.
  而clr的管理就是完全面向應(yīng)用域一級(jí).clr不能卸載(unload)某個(gè)類型或配件,
必須以應(yīng)用域?yàn)閱挝粏?dòng)/停止代碼,獲取/釋放資源.
  clr在執(zhí)行一個(gè)配件時(shí),會(huì)新建一個(gè)應(yīng)用域,將此配件放入新的應(yīng)用域.如果多個(gè)應(yīng)用域
同時(shí)使用到一個(gè)配件,就要涉及到前面提到的配件載入優(yōu)化策略了.最簡單的方法是使用
startup_loader_optimization_single_domain標(biāo)志,每個(gè)應(yīng)用域擁有一份獨(dú)立的
配件的鏡像,這樣速度最快,管理最方便,但占用內(nèi)存較多.相對(duì)的是所有應(yīng)用域共享一份
配件的鏡像,(使用startup_loader_optimization_multi_domain標(biāo)志)
這樣節(jié)約內(nèi)存,但在此配件中存在靜態(tài)變量等數(shù)據(jù)時(shí),因?yàn)橐WC每個(gè)應(yīng)用域有獨(dú)立的數(shù)
據(jù),
所以會(huì)一定程度上影響效率.折中的方案是使用
(使用startup_loader_optimization_multi_domain_host標(biāo)志)
此時(shí),只有那些有strong name的配件才會(huì)被多個(gè)應(yīng)用域共享.
  這里又涉及到一個(gè)概念strong name.他是一個(gè)配件的身份證明,他由配件的
名字/版本/culture以及數(shù)字簽名等組成.在配件發(fā)布時(shí)用以區(qū)別不同版本.
也在安全/版本控制等方面起到重要作用,以后有機(jī)會(huì)會(huì)專門講解.暫且跳過.
  獲取了icorruntimehost接口的指針后,我們可以以此指針取得當(dāng)前/缺省應(yīng)用域,
并可枚舉clr引擎實(shí)例中所有的應(yīng)用域.
  ccomptr<iunknown> spunk;
  ccomptr<_appdomain> spappdomain;
  
  check(sphost->getdefaultdomain(&spunk));
  spappdomain = spunk; spunk = null;
  wcout << l"default appdomain is " << (wchar_t
*)spappdomain->getfriendlyname() << endl;
  
  check(sphost->currentdomain(&spunk));
  spappdomain = spunk; spunk = null;
  wcout << l"current appdomain is " << (wchar_t
*)spappdomain->getfriendlyname() << endl;
  
  hdomainenum henum;
  check(sphost->enumdomains(&henum));
  spunk = null;
  while(sphost->nextdomain(henum, &spunk) != s_false)
  {
    spappdomain = spunk; spunk = null;
    wcout << (wchar_t *)spappdomain->getfriendlyname() << endl;
  }
  check(sphost->closeenum(henum));
  當(dāng)前應(yīng)用域是指當(dāng)前線程運(yùn)行時(shí)所在應(yīng)用域.注意線程屬于進(jìn)程,但不屬于某個(gè)應(yīng)用域,
  
一個(gè)線程可以跨應(yīng)用域操作.可以通過線程類的thread.getdomain獲取線程當(dāng)前所在
應(yīng)用域.
  缺省應(yīng)用域是clr引擎載入后自動(dòng)建立的應(yīng)用域,其生命期貫串clr引擎的使用期,
一般在此應(yīng)用域中執(zhí)行clr host的managed code端管理代碼,而不執(zhí)行用戶代碼.
  接下來,是載入用戶代碼所在配件的時(shí)候了.方法有兩種,一是接著使用完全的
native code或者說unmanaged code通過bcl的com包裝接口操作;二是將操作
移交給managed code部分的clr host代碼執(zhí)行.后者實(shí)現(xiàn)簡單,速度較快.
筆者以后將單獨(dú)以一篇文章介紹clr host的managed code部分代碼的設(shè)計(jì)編寫.
這里將簡要介紹第一種實(shí)現(xiàn).
  以u(píng)nmanaged code完整實(shí)現(xiàn)clr host雖然麻煩,但功能更加強(qiáng)大.但因?yàn)椴僮髦?
要不斷在unmanaged/managed code之間切換,效率受到一定影響.(切換的調(diào)用
是通過idispatch接口實(shí)現(xiàn),本身效率就很低,加上ccw(com callable wrapper)
的包裝,低于直接使用managed code的效率.
  以u(píng)nmanaged code調(diào)用配件,必須知道配件的部分信息,如配件的名字,
要調(diào)用的類的名字,要調(diào)用的函數(shù)等等.可以指定參數(shù)的方式來使用,也可以通過
pe映像中clr頭的il入口entrypointtoken以及metadata的信息來獲取
(詳情請(qǐng)參看筆者《ms.net平臺(tái)下clr 擴(kuò)展pe結(jié)構(gòu)分析》一文metadata篇)
這里為了示例簡單,采用參數(shù)傳遞方式.
  if(argc < 4)
  {
    cerr << "usage: " << argv[0] << " <assembly name> <class name> <main
function name> <parameters>" << endl;
  }
  else
  {
    _bstr_t bstrassemblyname(argv[1]),
            bstrclassname(argv[2]),
            bstrmainfuncname(argv[3]);
    ...
  }
  例子中以命令行方式傳遞配件/類/函數(shù)名信息.
  spunk = null;
  check(sphost->getdefaultdomain(&spunk));
  spappdomain = spunk; spunk = null;
  首先獲取缺省應(yīng)用域,在此應(yīng)用域中創(chuàng)建指定配件中指定類.這里為例子簡潔
直接在缺省應(yīng)用域中載入配件,實(shí)際開發(fā)中應(yīng)避免這種方式,而采用建立新應(yīng)用域
的方式來載入配件.關(guān)于新建應(yīng)用域以及建立時(shí)的配置,設(shè)計(jì)問題較多,以后再專門
寫文章詳述,這里略去.
  _objecthandleptr spobj = spappdomain->createinstance(bstrassemblyname,
bstrclassname);
  ccomptr<idispatch> spdisp = spobj->unwrap().pdispval;
  建立配件中類實(shí)例后,取得一個(gè)_objecthandleptr類型值,
通過unwrap()調(diào)用獲取idispatch接口,然后就可以通過此接口,以傳統(tǒng)的com
方式控制clr中的類.
    int argcount = argc-4;
    dispid dispid;
    lpolestr rgszname = bstrmainfuncname;
    variantarg *pargs = new variantarg[argcount];
    for(int i=0; i<argcount; i++)
    {
      variantinit(&pargs[i]);
      pargs[i].vt = vt_bstr;
      pargs[i].bstrval = _bstr_t(argv[4+i]);
    }
    dispparams dispparamsnoargs = {pargs, null, argcount, 0};
  
    check(spdisp->getidsofnames(iid_null, &rgszname, 1,
locale_system_default, &dispid));
    check(spdisp->invoke(dispid, iid_null, locale_system_default,
dispatch_method,
      &dispparamsnoargs, null, null, null));
    delete[] pargs;
  以上例子代碼,將命令行傳入?yún)?shù)放入?yún)?shù)數(shù)組,以idispatch->invoke調(diào)用指定名字
的方法.其后臺(tái)操作均由ccw進(jìn)行傳遞.如果要直接運(yùn)行一個(gè)assembly,可以使用
iappdomain.executeassembly更加便捷.如
  check(spappdomain->executeassembly(bstrassemblyname, null));
  至此,一個(gè)簡單但完整的clr host程序就完成了,他可以以完全的unmanaged code
啟動(dòng)clr引擎,載入指定assembly,以指定參數(shù)運(yùn)行指定的類的方法.
  下面是完整的示例程序,vc7編譯通過,vc6修改一下應(yīng)該也沒有問題.
  
hello.cs
  
using system;
  
namespace hello
{
    /// <summary>
    /// summary description for class1.
    /// </summary>
    public class hello
    {
        public void sayhello(string name)
        {
                console.writeline("hello "+name);
        }
    }
}
  
clrhost.cpp
  
// clrhost.cpp : defines the entry point for the console application.
//
  
#include "stdafx.h"
  
#include <mscoree.h>
  
#import <mscorlib.tlb> rename("reportevent", "reportevent_")
using namespace mscorlib;
  
#include <assert.h>
  
#include <string>
#include <memory>
#include <iostream>
using namespace std;
  
typedef hresult (__stdcall * getinfofunc)(lpwstr pbuffer, dword cchbuffer,
dword* dwlength);
  
#define check(v) /
  if(failed(v)) /
    cerr << "com function call failed - " << getlasterror() << " at " <<
__file__ << ", " << __line__ << endl;
  
wstring getinfo(getinfofunc func)
{
  wchar_t szbuf[max_path];
  dword dwlength;
  if(succeeded((func)(szbuf, max_path, &dwlength)))
    return wstring(szbuf, dwlength);
  else
    return null;
}
  
int _tmain(int argc, _tchar* argv[])
{
  ccomptr<icorruntimehost> sphost;
  
  check(corbindtoruntimeex(null, l"wks",
    startup_loader_optimization_single_domain | startup_concurrent_gc,
    clsid_corruntimehost, iid_icorruntimehost, (void **)&sphost));
  
  wcout << l"load clr " << getinfo(getcorversion)
        << l" from " << getinfo(getcorsystemdirectory)
        << endl;
  
  check(sphost->start());
  
  ccomptr<iunknown> spunk;
  ccomptr<_appdomain> spappdomain;
  
#ifdef _debug
  check(sphost->getdefaultdomain(&spunk));
  spappdomain = spunk; spunk = null;
  wcout << l"default appdomain is " << (wchar_t
*)spappdomain->getfriendlyname() << endl;
  
  check(sphost->currentdomain(&spunk));
  spappdomain = spunk; spunk = null;
  wcout << l"current appdomain is " << (wchar_t
*)spappdomain->getfriendlyname() << endl;
  
  hdomainenum henum;
  check(sphost->enumdomains(&henum));
  spunk = null;
  while(sphost->nextdomain(henum, &spunk) != s_false)
  {
    spappdomain = spunk; spunk = null;
    wcout << (wchar_t *)spappdomain->getfriendlyname() << endl;
  }
  check(sphost->closeenum(henum));
#endif // _debug
  
  if((argc < 2) || (argc == 3))
  {
    cerr << "usage: " << argv[0] << " <assembly name> <class name> <main
function name> <parameters>" << endl;
  }
  else
  {
    spunk = null;
    check(sphost->getdefaultdomain(&spunk));
    spappdomain = spunk; spunk = null;
  
    _bstr_t bstrassemblyname(argv[1]);
    if(argc == 2)
    {
      check(spappdomain->executeassembly(bstrassemblyname, null));
    }
    else
    {
      _bstr_t bstrclassname(argv[2]),
              bstrmainfuncname(argv[3]);
  
      _objecthandleptr spobj =
spappdomain->createinstance(bstrassemblyname, bstrclassname);
      ccomptr<idispatch> spdisp = spobj->unwrap().pdispval;
  
      dispid dispid;
      lpolestr rgszname = bstrmainfuncname;
      dispparams dispparamsargs = {null, null, 0, 0};
  
      int argcount = argc-4;
      if(argcount > 0)
      {
        dispparamsargs.cargs = argcount;
        dispparamsargs.rgvarg = new variantarg[argcount];
        variantarg *pargs = dispparamsargs.rgvarg;
        for(int i=0; i<argcount; i++)
        {
          variantinit(&pargs[i]);
          pargs[i].vt = vt_bstr;
          pargs[i].bstrval = _bstr_t(argv[4+i]);
        }
      }
  
      check(spdisp->getidsofnames(iid_null, &rgszname, 1,
locale_system_default, &dispid));
      check(spdisp->invoke(dispid, iid_null, locale_system_default,
dispatch_method,
        &dispparamsargs, null, null, null));
      delete[] dispparamsargs.rgvarg;
    }
  }
  
  check(sphost->stop());
  
    return 0;
}
  
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 宁晋县| 齐齐哈尔市| 杭州市| 柞水县| 即墨市| 邹城市| 通道| 南宁市| 卢湾区| 仙居县| 织金县| 宜黄县| 永福县| 土默特左旗| 定远县| 梁山县| 荔浦县| 高安市| 玛纳斯县| 东港市| 麻阳| 肇州县| 昭通市| 宁强县| 新龙县| 茂名市| 梁山县| 南部县| 临城县| 宜城市| 当雄县| 星子县| 海城市| 永登县| 锦州市| 泰宁县| 常山县| 达孜县| 河津市| 来安县| 当雄县|