InProc-SxS是.Net 4.0新推出的功能。在.Net 2.0中,一个进程中只能运行一个版本的.Net的运行时(Runtime)。在.Net 4.0中,我们可以在一个进程中运行多个不同版本的.Net运行时。也就是说在.Net 4.0,我们可以把进程的一部分的运行在.Net 4.0上,而另一部分运行在.Net 2.0上。我们可以把进程、运行时和程序域(AppDomain)用一个图来表示:
引入Inproc-SxS,给我们构建应用程序带来了很大的灵活性。在很多大型软件中,都允许第三方开发商添加插件(Add-in)。有了Inproc-SxS之后,不同的插件就可以运行在不同的.Net运行时上了。
比如一个软件开发商推出了一个基于.Net 4.0的软件,另一第三方软件开发商开发了该软件的一个插件。该插件的开发商还没有升级到.Net 4.0,仍然基于.Net 2.0开发软件,因此他们的插件还没有在4.0上做过任何测试。如果没有Inproc-SxS,该插件和它的宿主(Host)软件一样,都运行在.Net 4.0。由于插件并没有在4.0下测试过,运行在4.0运行时中就可能会有问题。现在有了Inproc-SxS,我们可以把宿主软件运行在.Net 4.0上,而插件运行在.Net 2.0上。
可能有人会问:.Net应该做到向下兼容,那基于.Net 2.0开发的插件在4.0上运行怎么会有问题呢?的确,微软的.Net 相应的开发团队在向下兼容性方面做了大量的工作。但100%的向下兼容只可能是一个理论上的目标。实际上在推出一个新版本的时候,总会对上一个版本会有若干不兼容的改动。如果用户在某一特定功能上仍然需要上一个版本,他可以利用Inproc-SxS把该功能单独运行在上一个版本的.Net中。
下面通过一个实例来讨论如何在Winforms中应用Inproc-SxS。这个实例分为三部分:第一部分是Winforms插件,第二部分负责把插件在某一特定版本的.Net中运行,第三部分是一个宿主应用程序。
如下图所示,我们在一个UserControl(class名为CLRVersionControl)里添加一个GroupBox,一个Label和一个PropertyGrid:
为了演示需要,我们在Load的事件处理器(Event Handler)中,把GroupBox的Text设为当前程序域的名字,而把Label的Text设为当前.Net运行时的版本:
private void CLRVersionControl_Load(object sender, EventArgs e)
{
groupBox1.Text = AppDomain.CurrentDomain.FriendlyName;
label1.Text = "CLR version: " + Environment.Version.ToString();
}
同时,我们添加一个函数,用来生成该插件的一个实例,并返回该插件的句柄(Handle)。它的宿主应用程序可以通过该函数创建一个插件,并把该插件添加到宿主中。
public static IntPtr CreateCLRVersionControl()
{
Control control = new CLRVersionControl();
return control.Handle;
}
这一部分的功能分为两部分:首先我们要创建某一版本的.Net运行时;接着我们在.Net运行时中创建一个程序域的实例,并在该程序域中创建一个插件的实例。
.Net提供了接口ICorRuntimeHost、ICLRMetaHost和 ICLRRuntimeInfo来运行某一版本的.Net。下面是一段代码范例。读者可以查阅MSDN对应的帮助文档,我在此不再详细介绍这些API。
CComPtr<ICorRuntimeHost> LoadRunTime(LPCWSTR strVersion)
{
CComPtr<ICorRuntimeHost> runtimeHost = NULL;
CComPtr<ICLRMetaHost> metahost;
CComPtr<ICLRRuntimeInfo> rtInfo;
HMODULE hmscoree = LoadLibrary(L"mscoree.dll");
if(hmscoree == NULL) return NULL;
PGETCLRMETAHOST getClrMetaHost = (PGETCLRMETAHOST)GetProcAddress(hmscoree,
"GetCLRMetaHost");
if(getClrMetaHost == NULL) return NULL;
HRESULT hr = getClrMetaHost(__uuidof(ICLRMetaHost), (LPVOID*)&metahost);
if(SUCCEEDED(hr))
{
hr = metahost->GetRuntime(strVersion,
IID_ICLRRuntimeInfo,
(LPVOID*)&rtInfo);
if(SUCCEEDED(hr))
{
hr = rtInfo->GetInterface(CLSID_CorRuntimeHost,
IID_ICorRuntimeHost,
(LPVOID*)&runtimeHost);
if(SUCCEEDED(hr))
{
hr = runtimeHost->Start();
if(FAILED(hr)) runtimeHost.Release();
}
}
}
if(hmscoree) FreeLibrary(hmscoree);
if(FAILED(hr))
{
MessageBox(NULL, L"CLR Load Failed", L"WinFormsSxSTest", MB_OK);
}
return runtimeHost;
}
我们在上述代码中输入某一个.Net的版本号,就能得到该版本号对应的.Net运行时。接下来我们在一个.Net运行时里创建一个程序域,并创建一个Winforms插件的实例:
int CreateControl(CComPtr<ICorRuntimeHost> runtimeHost,
LPCWSTR strAppDomainName,
LPCWSTR strAssemblyName,
LPCWSTR strTypeName,
LPCWSTR strMethodName)
{
CComPtr<IUnknown> punkAD;
HRESULT hr = runtimeHost->CreateDomain(strAppDomainName, NULL, &punkAD);
int retValue = 0;
if(SUCCEEDED(hr))
{
CComPtr<mscorlib::_AppDomain> srpAD;
hr = punkAD.QueryInterface(&srpAD);
if(SUCCEEDED(hr))
{
long hashCode = 0;
srpAD->raw_GetHashCode(&hashCode);
}
if(SUCCEEDED(hr))
{
CComPtr<mscorlib::_Assembly> srpAsm;
CComBSTR asmName = strAssemblyName;
hr = srpAD->raw_Load_2(asmName, &srpAsm);
if(SUCCEEDED(hr))
{
CComPtr<mscorlib::_Type> srpType;
CComBSTR typeName = strTypeName;
hr = srpAsm->raw_GetType_2(typeName, &srpType);
if(SUCCEEDED(hr) && srpType != NULL)
{
CComBSTR methodName = strMethodName;
CComVariant varEmpty, varRet;
hr = srpType->raw_InvokeMember(methodName,
(mscorlib::BindingFlags)
(mscorlib::BindingFlags_InvokeMethod |
mscorlib::BindingFlags_Public |
mscorlib::BindingFlags_Static),
NULL, varEmpty, NULL, NULL, NULL, NULL, &varRet);
retValue = varRet.llVal;
}
}
}
}
if(FAILED(hr))
{
MessageBox(NULL, L"WinForm Load Failed", L"Error", MB_OK);
}
return retValue;
}
上述代码用调用ICorRuntimeHost::CreateDomain在某版本的.Net运行时中创建一个程序域,让后在该该程序域中调用CLRVersionControl.CreateCLRVersionControl创建Winforms插件CLRVersionControl的一个实例。
有了前面的准备,我们就可以在一个特定的运行时里创建运行CLRVersionControl的实例:
extern "C" __declspec (dllexport) int __stdcall CreateCLRVersionControl
(
LPCWSTR strVersion,
LPCWSTR strAppDomainName,
LPCWSTR strAssemblyName,
LPCWSTR strTypeName,
LPCWSTR strMethodName
)
{
CComPtr<ICorRuntimeHost> runtimeHost = LoadRunTime(strVersion);
return CreateControl(runtimeHost,
strAppDomainName,
strAssemblyName,
strTypeName,
strMethodName);
}
创建一个基于对话框的MFC项目,在该对话框上添加两个Button,如下图所示:
为第一个button的click添加响应函数:
CWnd* pWndNET2Controls;
void CApplicationHostDlg::OnBnClickedButton1()
{
if(pWndNET2Controls != NULL)
return;
int handle = CreateCLRVersionControl(L"v2.0.50727",
L"AppDomain A",
L"WindowsFormsControlLibrary1",
L"InProcSxSDemo.CLRVersionControl",
L"CreateCLRVersionControl");
pWndNET2Controls = new CWnd();
pWndNET2Controls->Attach((HWND)handle);
pWndNET2Controls->SetParent(this);
// set size and position
……
}
在上述代码中,v2.0.50727是.Net 2.0的版本号。我们根据返回的插件句柄创建一个窗口,并将该插件的窗口作为子窗口添加到对话框中。
同样我们也为第二个button添加click的响应函数:
CWnd* pWndNET4Controls;
void CApplicationHostDlg::OnBnClickedButton2()
{
if(pWndNET4Controls != NULL)
return;
int handle = CreateCLRVersionControl(L"v4.0.21002",
L"AppDomain B",
L"WindowsFormsControlLibrary1",
L"InProcSxSDemo.CLRVersionControl",
L"CreateCLRVersionControl");
pWndNET4Controls = new CWnd();
pWndNET4Controls->Attach((HWND)handle);
pWndNET4Controls->SetParent(this);
// set size and position
……
}
V4.0.21002是.Net 4.0 Beta2的版本号。读者安装了的.Net 4.0的版本号可能会有所不同,请做相应修改。和前面一样,我们也将插件的窗口作为子窗口添加到对话框中。
现在我们运行我们宿主软件,并先后点击两个button,得到如下效果:
通过上面的截图,我们可以看出来,左边的插件时运行在.Net 2.0中,而右边插件运行在.Net 4.0中。如果仔细观察,我们还能发现在两个插件的PropertyGrid中略有不同:左边的PropertyGrid用+或者-来表示树状结点的打开或者收拢的状态,而右边的PropertyGrid用风格的三角形来表述树状结点的状态。这是因为在.Net 4.0中,Winforms对PropertyGrid做了一点改动,使PropertyGrid的树状结构和Vista、Windows 7的风格保持一致。