使用配置为.Net 程序集创建多个版本的发布

  在某些情况下,我们需要将程序发布在由于各种差异所构成的不同场景下,这些差异可能来自于面向的框架的,第三方SDK,系统(x86,x64)的版本等,幸运的是,得益于JIT的特性,在CLR版本一致,且类和方法的参数,数量等关键签名信息未发生变化时,.Net程序具备一定的向下兼容性,通俗地讲,即是编译后的程序集能够正常使用该程序集在编译前所引用的程序集的更高版本的能力,当然,须保证被引用的程序集的两个版本所被定义的类,方法等签名相同,以及所使用的CLR版本一致。关于.net 目标版本与CLR的兼容性详见MSDN:https://docs.microsoft.com/zh-cn/dotnet/framework/migration-guide/application-compatibility

  在满足以上条件的部分情况下,CLR为我们解决了一些潜在的版本兼容问题,但是在相对复杂的情况下(通常表现为,低版本的运行环境无法满足更高版本的程序集的运行要求,特别是在程序集的依赖链关系比较复杂,甚至依赖非托管程序集的时候),CLR表示臣妾无能为力了,在程序出现了类型加载错误等运行时错误后,需要程序员自行对程序集的依赖关系进行逐步分析,调试以解决问题,这个过程比较耗时耗力。

  引子

  RevitLookUp是一款基于Autodesk Revit建筑软件的二次开发插件,LookUp,顾名思义,该插件的目的既是查看,具体功能为在Revit软件运行时通过属性查看器的方式查询文档元素的信息,以便于开发人员进行RevitSDK的学习,和数据的分析,调试,该项目开源在Github上:https://github.com/jeremytammik/RevitLookup Revit在不同的时间端具有多个不同的发行版本,而不同的版本间则提供了对应的.net SDK,这些SDK(包括RevitAPI,RevitAPIUI)的绝大多内容都是一致的,但是依然具备着少量的差异,并且这些SDK还依赖了一些非托管程序集,这些情况导致了针对不同版本的Revit插件程序集将不能在不同版本间运行。而根据我对RevitLookUp代码库的发行记录理解,作者针对这个问题所使用的方式即为:在不同的时间下,基于不同版本的Revit SDK,根据迭代的情况发布不同的代码版本,亦即作者将只对使用最新版本SDK的代码进行功能更新和维护,对于使用更旧版本的SDK的代码只能进行维护,这样是无可厚非的,但是在长期维护这个项目的过程中,需要花费一些精力。那么有没有一种办法能够在只维护一份代码的情况下进行多个版本的发布和迭代呢?当然,方法是存在的,并且不少,这里将介绍一种在编译前使用配置发布适用于不同场景的程序集的方法。

  首先我们使用文本编辑器打开一份常规的C#项目文件(.csproj),可以看到如下内容:

<PropertyGroup>
    <ProjectType>Local</ProjectType>
    <ProductVersion>9.0.30729</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{05C77115-2277-4DFC-8F95-BE37E5F1130F}</ProjectGuid>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ApplicationIcon>
    </ApplicationIcon>
    <AssemblyKeyContainerName>
    </AssemblyKeyContainerName>
    <AssemblyName>RevitLookup</AssemblyName>
    <AssemblyOriginatorKeyFile>
    </AssemblyOriginatorKeyFile>
    <DefaultClientScript>JScript</DefaultClientScript>
    <DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout>
    <DefaultTargetSchema>IE50</DefaultTargetSchema>
    <DelaySign>false</DelaySign>
    <OutputType>Library</OutputType>
    <RootNamespace>RevitLookup</RootNamespace>
    <StartupObject>
    </StartupObject>
    <FileUpgradeFlags>
    </FileUpgradeFlags>
    <UpgradeBackupLocation>
    </UpgradeBackupLocation>
    <OldToolsVersion>3.5</OldToolsVersion>
    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
    <PublishUrl>publish\</PublishUrl>
    <Install>true</Install>
    <InstallFrom>Disk</InstallFrom>
    <UpdateEnabled>false</UpdateEnabled>
    <UpdateMode>Foreground</UpdateMode>
    <UpdateInterval>7</UpdateInterval>
    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
    <UpdatePeriodically>false</UpdatePeriodically>
    <UpdateRequired>false</UpdateRequired>
    <MapFileExtensions>true</MapFileExtensions>
    <ApplicationRevision>0</ApplicationRevision>
    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
    <IsWebBootstrapper>false</IsWebBootstrapper>
    <UseApplicationTrust>false</UseApplicationTrust>
    <BootstrapperEnabled>true</BootstrapperEnabled>
    <TargetFrameworkProfile />
  </PropertyGroup> 

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <OutputPath>bin\Debug\</OutputPath>
    <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
    <BaseAddress>285212672</BaseAddress>
    <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
    <ConfigurationOverrideFile>
    </ConfigurationOverrideFile>
    <DefineConstants>TRACE;DEBUG;ADSK_INTERNAL</DefineConstants>
    <DocumentationFile>
    </DocumentationFile>
    <DebugSymbols>true</DebugSymbols>
    <FileAlignment>4096</FileAlignment>
    <Optimize>false</Optimize>
    <RegisterForComInterop>false</RegisterForComInterop>
    <RemoveIntegerChecks>false</RemoveIntegerChecks>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <WarningLevel>4</WarningLevel>
    <DebugType>full</DebugType>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
    <Prefer32Bit>false</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <OutputPath>bin\Release\</OutputPath>
    <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
    <BaseAddress>285212672</BaseAddress>
    <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
    <ConfigurationOverrideFile>
    </ConfigurationOverrideFile>
    <DefineConstants>TRACE</DefineConstants>
    <DocumentationFile>
    </DocumentationFile>
    <DebugSymbols>false</DebugSymbols>
    <FileAlignment>4096</FileAlignment>
    <Optimize>true</Optimize>
    <RegisterForComInterop>false</RegisterForComInterop>
    <RemoveIntegerChecks>false</RemoveIntegerChecks>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <WarningLevel>4</WarningLevel>
    <DebugType>none</DebugType>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
    <Prefer32Bit>false</Prefer32Bit>
  </PropertyGroup>

  此处,我们省略了根元素,截取了其中部分关键的配置内容,暂时忽略那些繁多的配置节(这包含了程序集的生成路径,程序集的目标框架版本等),让我们将目光聚焦在这三个元素上:

<PropertyGroup>
...
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'D2018|AnyCPU'">
...
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 
'R2018|AnyCPU'">
...
</PropertyGroup>

  作为开发人员的你,应该不难猜到,第一个PropertyGroup是对于通用情况下的某些配置进行设定,后两者则是分别针对调试和发布模式的相关配置进行设定,他们使用Condition属性作为控制的变量,PropertyGroup能够对项目的公共属性进行描述,下面的ItemGroup则描述了其它更加具体的信息,这包含了项目的资源,代码,引用文件等。

 

<ItemGroup>
    <Reference Include="AdWindows">
      <HintPath>..\libs\Revit2017\AdWindows.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RevitAPI">
      <HintPath>..\libs\Revit2017\RevitAPI.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RevitAPIUI">
      <HintPath>..\libs\Revit2017\RevitAPIUI.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>

  此处我使用了RevitLookUp的Revit SDK引用配置内容,它所使用的版本为Revit2017.

  事实上,ItemGroup也具备Condition属性使得其在不同情况下生效。

  那么,聪明的开发者应该不难想到,我们可以使用PropertyGroup,ItemGroup的这一特性为项目加入更多的配置选项,以解决框架和SDK版本的多发行目的:

  我们首先使用解决方案的配置管理器,针对三个Revit SDK(2017,2018,2019)的版本和运行时框架,为解决方案和项目加入这些配置选项,完成后的配置是这样的:

  

 

  随后我们将卸载RevitLookUp项目,修改该项目的配置文件如下:

  

 <ItemGroup Condition="'$(Configuration)' == 'D2017' or '$(Configuration)' == 'R2017'">
    <Reference Include="AdWindows">
      <HintPath>..\libs\Revit2017\AdWindows.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RevitAPI">
      <HintPath>..\libs\Revit2017\RevitAPI.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RevitAPIUI">
      <HintPath>..\libs\Revit2017\RevitAPIUI.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup Condition="'$(Configuration)' == 'D2018' or '$(Configuration)' == 'R2018'">
    <Reference Include="AdWindows">
      <HintPath>..\libs\Revit2018\AdWindows.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RevitAPI">
      <HintPath>..\libs\Revit2018\RevitAPI.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RevitAPIUI">
      <HintPath>..\libs\Revit2018\RevitAPIUI.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>

  此时我仅列出了两个配置节,以说明不同的配置是如何引用不同的RevitSDK程序集。

  由于不同的Revit SDK程序集所使用的.net framework框架版本可能不同,我们还将对PropertyGroup进行修改。

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'D2017|AnyCPU'">
        ...
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'R2017|AnyCPU'">
       ...
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'D2018|AnyCPU'">
        ...
    <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'R2018|AnyCPU'">
    ...
    <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
  </PropertyGroup>

  另外,不同版本的SDK在部分接口定义上也有差异,这个问题我们通过给不同的配置选项定义不同的编译时常量,以使用预编译手段解决接口间的差异问题,上述的所有配置过程不能通过Visual Studio的UI实现,而编译时常量是可以被UI操作的:

  

  在代码中使用如下的方式,进行条件编译:

  

#if V2017
using ASTNameSpace = Autodesk.Revit.Utility;
#else
using ASTNameSpace = Autodesk.Revit.DB.Visual;
#endif

  

  此方式的所有操作细节介绍完毕,该Fork的源码托管在https://github.com/JanusTida/RevitLookup感谢耐心看完的你:( 记得投币,点赞...啊呸!评论和指正...

 

posted @ 2019-06-18 17:50  .NetDomainer  阅读(805)  评论(2编辑  收藏  举报