进程内并行执行

注释

本文特定于 .NET Framework。 它不适用于 .NET 的较新版本实现,包括 .NET 6 及更高版本。

从 .NET Framework 4 开始,可以使用进程内并行托管在单个进程中运行多个版本的公共语言运行时 (CLR)。 默认情况下,托管 COM 组件使用其生成所用的 .NET Framework 版本运行,而不考虑为进程加载的 .NET Framework 版本。

背景

.NET Framework 始终为托管代码应用程序提供并行托管,但在 .NET Framework 4 之前,它未为托管 COM 组件提供该功能。 过去,加载到进程中的托管 COM 组件运行时,会使用已加载的运行时版本或最新安装的 .NET Framework 版本。 如果此版本与 COM 组件不兼容,则组件将失败。

.NET Framework 4 提供了一种并行托管的新方法,可确保以下各项:

  • 安装新版本的 .NET Framework 不会影响现有应用程序。

  • 应用程序在其构建时所使用的 .NET Framework 版本上运行。 除非明确指示这样做,否则它们不使用 .NET Framework 的新版本。 但是,应用程序更容易过渡到使用新版本的 .NET Framework。

对用户和开发人员的影响

  • 最终用户和系统管理员。 现在,这些用户可以更加确信,当他们独立安装新版本的运行时或应用程序时,它不会影响他们的计算机。 现有应用程序将继续像以前一样运行。

  • 应用程序开发人员。 并行托管几乎对应用程序开发人员没有影响。 默认情况下,应用程序始终运行在它们构建的 .NET Framework 版本之上,这一点没有改变。 但是,开发人员可以重写此行为,并指示应用程序在较新版本的 .NET Framework 下运行(请参阅 方案 2)。

  • 库开发人员和使用者。 并行托管无法解决库开发人员面临的兼容性问题。 应用程序直接加载的库(通过直接引用或通过 Assembly.Load 调用)会继续使用其加载到的 AppDomain 运行时。 应针对要支持的所有 .NET Framework 版本测试库。 如果使用 .NET Framework 4 运行时编译应用程序,但包含使用早期运行时生成的库,该库也将使用 .NET Framework 4 运行时。 但是,如果你有使用早期运行时和使用 .NET Framework 4 生成的库的应用程序,则必须强制应用程序也使用 .NET Framework 4(请参阅 方案 3)。

  • 托管 COM 组件开发人员。 过去,托管 COM 组件使用计算机上安装的最新版本的运行时自动运行。 现在可以针对生成的运行时版本执行 COM 组件。

    如下表所示,使用 .NET Framework 版本 1.1 生成的组件可以与版本 4 组件并行运行,但它们不能与版本 2.0、3.0 或 3.5 组件一起运行,因为并行托管不适用于这些版本。

    .NET Framework 版本 1.1 2.0 - 3.5 4
    1.1 不適用 是的
    2.0 - 3.5 不適用 是的
    4 是的 是的 不適用

注释

.NET Framework 版本 3.0 和 3.5 以增量方式在版本 2.0 上生成,无需并行运行。 这些本质上是相同的版本。

常见的并行托管方案

  • 方案 1: 使用由旧版 .NET Framework 生成的 COM 组件的本机应用程序。

    已安装的 .NET Framework 版本:.NET Framework 4 和 COM 组件使用的 .NET Framework 的所有其他版本。

    怎么做:在这种情况下,什么都不用做。 COM 组件将使用注册到的 .NET Framework 版本运行。

  • 方案 2:使用 .NET Framework 2.0 SP1 生成的托管应用程序,你希望使用 .NET Framework 2.0 运行,但如果版本 2.0 不存在,则愿意在 .NET Framework 4 上运行。

    已安装的 .NET Framework 版本:.NET Framework 和 .NET Framework 4 的早期版本。

    应该怎么做:在应用程序目录中的 应用程序配置文件 中,使用 <启动> 元素<支持的运行时> 元素 设置如下:

    <configuration>
      <startup >
        <supportedRuntime version="v2.0.50727" />
        <supportedRuntime version="v4.0" />
      </startup>
    </configuration>
    
  • 方案 3: 本机应用程序使用用早期版本 .NET Framework 构建的 COM 组件,并希望与 .NET Framework 4 一起运行。

    已安装的 .NET Framework 版本:.NET Framework 4。

    执行步骤:在应用程序目录中的应用程序配置文件中,使用<startup>元素,并将useLegacyV2RuntimeActivationPolicy属性设置为true,同时按照如下方式设置<supportedRuntime>元素。

    <configuration>
      <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v4.0" />
      </startup>
    </configuration>
    

示例:

以下示例演示了一个非托管 COM 主机,该主机使用编译该组件的 .NET Framework 版本来运行托管 COM 组件。

若要运行以下示例,请使用 .NET Framework 3.5 编译并注册以下托管 COM 组件。 若要注册组件,请在“ 项目 ”菜单上单击“ 属性”,单击“ 生成 ”选项卡,然后选择“ 注册 COM 互作 ”复选框。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace BasicComObject
{
    [ComVisible(true), Guid("9C99C4B5-CA54-4c58-8988-49B6811BA53B")]
    public class MyObject : SimpleObjectModel.IPrintInfo
    {
        public MyObject()
        {
        }
        public void PrintInfo()
        {
            Console.WriteLine("MyObject was activated in {0} runtime in:\n\tAppDomain {1}:{2}", System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion(), AppDomain.CurrentDomain.Id, AppDomain.CurrentDomain.FriendlyName);
        }
    }
}

编译以下非托管C++应用程序,该应用程序激活上一示例创建的 COM 对象。

#include "stdafx.h"
#include <string>
#include <iostream>
#include <objbase.h>
#include <string.h>
#include <process.h>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    char input;
    CoInitialize(NULL) ;
    CLSID clsid;
    HRESULT hr;
    HRESULT clsidhr = CLSIDFromString(L"{9C99C4B5-CA54-4c58-8988-49B6811BA53B}",&clsid);
    hr = -1;
    if (FAILED(clsidhr))
    {
        printf("Failed to construct CLSID from String\n");
    }
    UUID id = __uuidof(IUnknown);
    IUnknown * pUnk = NULL;
    hr = ::CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,id,(void **) &pUnk);
    if (FAILED(hr))
    {
        printf("Failed CoCreateInstance\n");
    }else
    {
        pUnk->AddRef();
        printf("Succeeded\n");
    }

    DISPID dispid;
    IDispatch* pPrintInfo;
    pUnk->QueryInterface(IID_IDispatch, (void**)&pPrintInfo);
    OLECHAR FAR* szMethod[1];
    szMethod[0]=OLESTR("PrintInfo");
    hr = pPrintInfo->GetIDsOfNames(IID_NULL,szMethod, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    DISPPARAMS dispparams;
    dispparams.cNamedArgs = 0;
    dispparams.cArgs = 0;
    VARIANTARG* pvarg = NULL;
    EXCEPINFO * pexcepinfo = NULL;
    WORD wFlags = DISPATCH_METHOD ;
;
    LPVARIANT pvRet = NULL;
    UINT * pnArgErr = NULL;
    hr = pPrintInfo->Invoke(dispid,IID_NULL, LOCALE_USER_DEFAULT, wFlags,
        &dispparams, pvRet, pexcepinfo, pnArgErr);
    printf("Press Enter to exit");
    scanf_s("%c",&input);
    CoUninitialize();
    return 0;
}

另请参阅