STARTUP_CONCURRENT_GC,
CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void **)&spHost));
这行代码载入最高版本CLR的wks类型运行库,为单应用域进行优化并使用并发GC机制.
前面提到了配件载入优化策略,要理解这个概念,我们必须先了解应用域的概念.
传统Win程序中,资源的分配管理单位是进程,操作系统以进程边界将应用程序实例隔离开
,
单个进程的崩溃不会对其他进程产生直接影响,进程也不能直接使用其他进程的资源.
进程很好,但使用进程的代价太大,为此Win32引入了线程的概念.同一进程中的线程能够
共享资源,线程管理和切换的代价也远远小于进程.但因为在同一进程中,线程的崩溃会直
接
影响到其他线程的运行,也无法约束线程间数据的直接访问等等.
为此,CLR中引入了Application Domain应用域的概念.应用域是介于进程和线程
之间的一种逻辑上的概念.他既有线程轻巧,管理切换快捷的优点,也有进程在稳定性方面
的优点,单个应用域的崩溃不会直接影响到同一进程中的其他应用域,应用域也无法直接
访问同一进程中的其他应用域的资源,这方面和进程完全相同.
而CLR的管理就是完全面向应用域一级.CLR不能卸载(Unload)某个类型或配件,
必须以应用域为单位启动/停止代码,获取/释放资源.
CLR在执行一个配件时,会新建一个应用域,将此配件放入新的应用域.如果多个应用域
同时使用到一个配件,就要涉及到前面提到的配件载入优化策略了.最简单的方法是使用
STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN标志,每个应用域拥有一份独立的
配件的镜像,这样速度最快,管理最方便,但占用内存较多.相对的是所有应用域共享一份
配件的镜像,(使用STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN标志)
这样节约内存,但在此配件中存在静态变量等数据时,因为要保证每个应用域有独立的数
据,
所以会一定程度上影响效率.折中的方案是使用
(使用STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST标志)
此时,只有那些有Strong Name的配件才会被多个应用域共享.
这里又涉及到一个概念Strong Name.他是一个配件的身份证明,他由配件的
名字/版本/culture以及数字签名等组成.在配件发布时用以区别不同版本.
也在安全/版本控制等方面起到重要作用,以后有机会会专门讲解.暂且跳过.
获取了ICorRuntimeHost接口的指针后,我们可以以此指针取得当前/缺省应用域,
并可枚举CLR引擎实例中所有的应用域.
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));
当前应用域是指当前线程运行时所在应用域.注意线程属于进程,但不属于某个应用域,
一个线程可以跨应用域操作.可以通过线程类的Thread.GetDomain获取线程当前所在
应用域.
缺省应用域是CLR引擎载入后自动建立的应用域,其生命期贯串CLR引擎的使用期,
一般在此应用域中执行CLR Host的Managed Code端管理代码,而不执行用户代码.
接下来,是载入用户代码所在配件的时候了.方法有两种,一是接着使用完全的
Native Code或者说Unmanaged Code通过BCL的COM包装接口操作;二是将操作
移交给Managed Code部分的CLR Host代码执行.后者实现简单,速度较快.
笔者以后将单独以一篇文章介绍CLR Host的Managed Code部分代码的设计编写.
这里将简要介绍第一种实现.
以Unmanaged Code完整实现CLR Host虽然麻烦,但功能更加强大.但因为操作中
要不断在Unmanaged/Managed Code之间切换,效率受到一定影响.(切换的调用
是通过IDispatch接口实现,本身效率就很低,加上CCW(COM Callable Wrapper)
的包装,低于直接使用Managed Code的效率.
以Unmanaged Code调用配件,必须知道配件的部分信息,如配件的名字,
要调用的类的名字,要调用的函数等等.可以指定参数的方式来使用,也可以通过
PE映像中CLR头的IL入口EntryPointToken以及Metadata的信息来获取
(详情请参看笔者《MS.Net平台下CLR 扩展PE结构分析》一文Metadata篇)
这里为了示例简单,采用参数传递方式.
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]);
...
}
例子中以命令行方式传递配件/类/函数名信息.
spUnk = NULL;
CHECK(spHost->GetDefaultDomain(&spUnk));
spAppDomain = spUnk; spUnk = NULL;
首先获取缺省应用域,在此应用域中创建指定配件中指定类.这里为例子简洁
直接在缺省应用域中载入配件,实际开发中应避免这种方式,而采用建立新应用域
的方式来载入配件.关于新建应用域以及建立时的配置,设计问题较多,以后再专门
写文章详述,这里略去.
_ObjectHandlePtr spObj = spAppDomain->CreateInstance(bstrAssemblyName,
bstrClassName);
CComPtr<IDispatch> spDisp = spObj->Unwrap().pdispVal;
建立配件中类实例后,取得一个_ObjectHandlePtr类型值,
通过Unwrap()调用获取IDispatch接口,然后就可以通过此接口,以传统的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;
以上例子代码,将命令行传入参数放入参数数组,以IDispatch->Invoke调用指定名字
的方法.其后台操作均由CCW进行传递.如果要直接运行一个Assembly,可以使用
IAppDomain.ExecuteAssembly更加便捷.如
CHECK(spAppDomain->ExecuteAssembly(bstrAssemblyName, NULL));
至此,一个简单但完整的CLR Host程序就完成了,他可以以完全的Unmanaged Code
启动CLR引擎,载入指定Assembly,以指定参数运行指定的类的方法.
下面是完整的示例程序,VC7编译通过,VC6修改一下应该也没有问题.
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
关键词:.Net平台下CLR程序载入原理区分