简体   繁体   English

相同的源,具有不同资源的多个目标(Visual Studio .Net 2008)

[英]Same source, multiple targets with different resources (Visual Studio .Net 2008)

A set of software products differ only by their resource strings, binary resources, and by the strings / graphics / product keys used by their Visual Studio Setup projects. 一组软件产品的区别仅在于其资源字符串,二进制资源以及Visual Studio安装项目使用的字符串/图形/产品密钥。 What is the best way to create, organize, and maintain them? 创建,组织和维护它们的最佳方法是什么?

ie All the products essentially consist of the same core functionality customized by graphics, strings, and other resource data to form each product. 即所有产品基本上由图形,字符串和其他资源数据定制的相同核心功能组成,以形成每个产品。 Imagine you are creating a set of products like "Excel for Bankers", Excel for Gardeners", "Excel for CEOs", etc. Each product has the the same functionality, but differs in name, graphics, help files, included templates etc. 想象一下,您正在创建一组产品,如“Excel for Bankers”,Excel for Gardeners“,”Excel for CEOs“等。每个产品都具有相同的功能,但名称,图形,帮助文件,包含的模板等不同。

The environment in which these are being built is: vanilla Windows.Forms / Visual Studio 2008 / C# / .Net. 构建这些环境的环境是:vanilla Windows.Forms / Visual Studio 2008 / C#/ .Net。

The ideal solution would be easy to maintain. 理想的解决方案很容易维护。 eg If I introduce a new string / new resource projects I haven't added the resource to should fail at compile time, not run time. 例如,如果我引入一个新的字符串/新资源项目,我没有添加资源,应该在编译时失败,而不是运行时。 (And subsequent localization of the products should also be feasible). (随后产品的本地化也应该是可行的)。

Hopefully I've missed the blindingly-obvious and easy way of doing all this. 希望我错过了做这一切的明显而简单的方法。 What is it? 它是什么?

============ Clarification(s) ================ ============澄清================

By "product" I mean the package of software that gets installed by the installer and sold to the end user. “产品”是指由安装程序安装并出售给最终用户的软件包。

Currently I have one solution, consisting of multiple projects, (including a Setup project), which builds a set of assemblies and create a single installer. 目前我有一个解决方案,包括多个项目(包括安装项目),它构建一组程序集并创建一个单独的安装程序。

What I need to produce are multiple products/installers, all with similar functionality, which are built from the same set of assemblies but differ in the set of resources used by one of the assemblies. 我需要生产的是多个产品/安装程序,它们都具有类似的功能,它们是从同一组程序集构建的,但其中一个程序集使用的资源集不同。 What's the best way of doing this? 这样做的最佳方法是什么?

------------ The 95% Solution ----------------- ------------ 95%的解决方案-----------------

Based upon Daminen_the_unbeliever's answer, a resource file per configuration can be achieved as follows: 根据Daminen_the_unbeliever的答案,每个配置的资源文件可以实现如下:

  1. Create a class library project ("Satellite"). 创建一个类库项目(“Satellite”)。
  2. Delete the default .cs file and add a folder ("Default") 删除默认的.cs文件并添加文件夹(“默认”)
  3. Create a resource file in the folder "MyResources" 在“MyResources”文件夹中创建资源文件
  4. Properties - set CustomToolNamespace to something appropriate (eg "XXX") 属性 - 将CustomToolNamespace设置为适当的值(例如“XXX”)
  5. Make sure the access modifier for the resources is "Public". 确保资源的访问修饰符是“Public”。 Add the resources. 添加资源。 Edit the source code. 编辑源代码。 Refer to the resources in your code as XXX.MyResources.ResourceName) 请参阅代码中的资源XXX.MyResources.ResourceName)
  6. Create Configurations for each product variant ("ConfigN") 为每个产品变体创建配置(“ConfigN”)
  7. For each product variant, create a folder ("VariantN") 对于每个产品变体,创建一个文件夹(“VariantN”)
  8. Copy and Paste the MyResources file into each VariantN folder 将MyResources文件复制并粘贴到每个VariantN文件夹中
  9. Unload the "Satellite" project, and edit the .csproj file 卸载“Satellite”项目,然后编辑.csproj文件
  10. For each "VariantN/MyResources" <Compile> or <EmbeddedResource> tag, add a Condition="'$(Configuration)' == 'ConfigN'" attribute. 对于每个“VariantN / MyResources” <Compile><EmbeddedResource>标记,添加Condition="'$(Configuration)' == 'ConfigN'"属性。
  11. Save, Reload the .csproj, and you're done... 保存,重新加载.csproj,你就完成了......

This creates a per-configuration resource file, which can (presumably) be further localized. 这将创建一个每配置资源文件,可以(可能)进一步本地化。 Compile error messages are produced for any configuration that where aa resource is missing. 为缺少资源的任何配置生成编译错误消息。 The resource files can be localized using the standard method (create a second resources file (MyResources.fr.resx) and edit .csproj as before). 资源文件可以使用标准方法进行本地化(创建第二个资源文件(MyResources.fr.resx)并像以前一样编辑.csproj)。

The reason this is a 95% solution is that resources used to initialize forms (eg Form Titles, button texts) can't be easily handled in the same manner - the easiest approach seems to be to overwrite these with values from the satellite assembly. 这是95%解决方案的原因是用于初始化表单的资源(例如表单标题,按钮文本)不能以相同的方式轻松处理 - 最简单的方法似乎是用来自附属程序集的值覆盖这些资源。

You can add conditionals to elements within the MSBuild file. 您可以向MSBuild文件中的元素添加条件。 So for instance, if you have "Debug" resources and "Release" resources, you can place these within two separate folders (eg Debug and Release). 因此,例如,如果您具有“调试”资源和“释放”资源,则可以将它们放在两个单独的文件夹中(例如,调试和发布)。 Then, within your MSBuild file you might have: 然后,在您的MSBuild文件中,您可能具有:

  <ItemGroup>
    <Compile Include="Debug\Resource1.Designer.cs" Condition=" '$(Configuration)' == 'Debug' ">
      <AutoGen>True</AutoGen>
      <DesignTime>True</DesignTime>
      <DependentUpon>Resource1.resx</DependentUpon>
    </Compile>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Queue.cs" />
    <Compile Include="Release\Resource1.Designer.cs" Condition=" '$(Configuration)' == 'Release' ">
      <AutoGen>True</AutoGen>
      <DesignTime>True</DesignTime>
      <DependentUpon>Resource1.resx</DependentUpon>
    </Compile>
    <Compile Include="Stack.cs" />
  </ItemGroup>
  <ItemGroup>
    <Content Include="XMLFile1.xml" />
  </ItemGroup>
  <ItemGroup>
    <EmbeddedResource Include="Debug\Resource1.resx" Condition=" '$(Configuration)' == 'Debug' ">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resource1.Designer.cs</LastGenOutput>
      <CustomToolNamespace>Resources</CustomToolNamespace>
    </EmbeddedResource>
    <EmbeddedResource Include="Release\Resource1.resx" Condition=" '$(Configuration)' == 'Release' ">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resource1.Designer.cs</LastGenOutput>
      <CustomToolNamespace>Resources</CustomToolNamespace>
    </EmbeddedResource>
  </ItemGroup>

Provided all of your access to your resources are via the Resources.Resource1 class, then you get two different sets of resources for debug and release builds. 如果您通过Resources.Resource1类对资源进行了所有访问,那么您将获得两组用于调试和发布版本的不同资源。 Obviously, this can be extended to further configurations. 显然,这可以扩展到进一步的配置。

Unfortunately, I don't think you can force the resources to use the same baseName (as provided to ResourceManager constructor), since it's based on the path within the project, and I can't find a way to override that. 不幸的是,我认为你不能强制资源使用相同的baseName(提供给ResourceManager构造函数),因为它基于项目中的路径,我找不到覆盖它的方法。 If you do need them to use the same name (if you're manually creating ResourceManagers, for example), then I'd suggest having a Resources1.resx (plus associated cs file) at the top level of the project, and not under source control. 如果你确实需要它们使用相同的名称(例如,如果你手动创建ResourceManagers),那么我建议在项目的顶层有一个Resources1.resx(以及相关的cs文件),而不是源控制。 As a pre-build event, copy the required .resx file out from the Debug or Release directory as appropriate. 作为预构建事件,根据需要从Debug或Release目录中复制所需的.resx文件。 (In this situation, you'd probably want to force it to not compile the .Designer.cs files within the subdirectories. (在这种情况下,您可能希望强制它不编译子目录中的.Designer.cs文件。

Edit 编辑

Forgot to mention (though it's seen in the above excerpt from the MSBuild file) that you have to set the Custom Tool Namespace on each .resx file to the same value (eg Resources), otherwise it also defaults to including the folder name. 忘记提及(虽然在上面的MSBuild文件摘录中看到)你必须将每个.resx文件上的自定义工具命名空间设置为相同的值(例如参考资料),否则它也默认包含文件夹名称。

Edit 2 编辑2

In response to query about checking that each resource file contains the same resources - If you're using the Resource class (eg Resources.Resource1.MyFirstStringResource) to access your resources, then switching configurations will result in build errors if the required resource doesn't exist, so you'll find that quite quickly. 响应关于检查每个资源文件是否包含相同资源的查询 - 如果您正在使用Resource类(例如Resources.Resource1.MyFirstStringResource)来访问您的资源,那么如果所需资源没有,则切换配置将导致构建错误。存在,所以你会很快发现。

For the truly paranoid (ie if your build process takes 3 days to build all configurations, or something equally mad), at the end of the day, .resx files are just XML files - you just need something to check that each .resx file with the same filename contains the same number of <data> elements, with the same name attributes. 对于真正的偏执狂(即如果你的构建过程需要3天来构建所有配置,或同样疯狂的东西),在一天结束时,.resx文件只是XML文件 - 你只需要检查每个.resx文件具有相同文件名的文件包含相同数量的<data>元素,具有相同的名称属性。

Here is my solution. 这是我的解决方案。 The original problem was that Autodesk releases every AutoCAD dlls with something different and breaks compatibility every 3 years. 最初的问题是Autodesk每个AutoCAD dll都会发布不同的东西,并且每3年就会破坏兼容性。 But our code is actually the same. 但是我们的代码实际上是一样的。 That was a painful point in the way of Develop-TFS-TFSbuild-DomainControllerInstallationOfApps. 这对Develop-TFS-TFSbuild-DomainControllerInstallationOfApps来说是一个痛苦的问题。

The solution is as follows : 解决方案如下:

  1. Make your MOTHER project with all the structure and, lets say the references for AutoCAD 2012. 制作具有所有结构的MOTHER项目,并举出AutoCAD 2012的参考资料。
  2. Then create second project, for AutoCAD 2013 in the same solution and reference its own DLLS. 然后在同一解决方案中为AutoCAD 2013创建第二个项目,并引用其自己的DLLS。
  3. Now you have to set one compilation symbol for each project - acad2012 in the first project and acad2013 for the second. 现在,您必须为每个项目设置一个编译符号 - 第一个项目中为acad2012,第二个项目为acad2013。
  4. Then in the 2013 project go to Add Existing Item and point to the sources of the 2012 project BUT THEN CLICK on the right of the Add button and point to Add As Link 然后在2013项目中转到添加现有项目并指向2012项目的来源,然后点击添加按钮右侧的点击并指向添加为链接
  5. If there is any library discrepancies between 2012 and 2013 you can surround them with #if acad2012 #endif or #if acad2013 #endif 如果2012年和2013年之间存在任何图书馆差异,您可以使用#if acad2012 #endif or #if acad2013 #endif包围它们

That way you will have only one source code to maintain and have to separately compiled modules :) 这样你只需要一个源代码来维护,并且必须单独编译模块:)

UPDATE UPDATE

What I need to produce are multiple products/installers, all with similar functionality, which are built from the same set of assemblies but differ in the set of resources used by one of the assemblies. 我需要生产的是多个产品/安装程序,它们都具有类似的功能,它们是从同一组程序集构建的,但其中一个程序集使用的资源集不同。 What's the best way of doing this? 这样做的最佳方法是什么?

  1. Setting up the Installers projects : This is exactly what we do and exactly what you need. 设置安装程序项目 :这正是我们所做的,也正是您所需要的。 After step 5 you must make two installer projects. 在步骤5之后,您必须进行两个安装程序项目 I mean Windows Installer Xml projects or WiX projects Lets say the first is for 2012 and one for 2013. The next problem is the generation of the XML files which contain all the components for the build. 我的意思是Windows Installer Xml项目或WiX项目让我们说第一个是2012年,一个是2013年。下一个问题是生成包含构建的所有组件的XML文件。 I use WiX toolkit with the following script : 我使用WiX工具包和以下脚本:

    heat.exe dir "$(TargetDir)" -cg MyAppComponents -dr INSTALLFOLDER -sfrag -srd -var "var.FilesPath" -out ".\\MyAppComponents.wxs" heat.exe目录“$(TargetDir)” - cg MyAppComponents -dr INSTALLFOLDER -sfrag -srd -var“var.FilesPath”-out“。\\ MyAppComponents.wxs”

This script builds the MyAppComponents.wxs with all the files you need. 此脚本使用您需要的所有文件构建MyAppComponents.wxs。 If you want to get acquainted with Wix go get this book : "WiX 3.6: A Developer's Guide to Windows Installer XML". 如果您想熟悉Wix,请阅读本书:“WiX 3.6:Windows Installer XML开发人员指南”。 I made everything needed after reading it. 读完之后,我做了所有需要的东西。

  1. Add MyAppComponents.wxs to your main installer project directly (lets it be for 2012) and in any other project "Add As Link" (let it be for 2013 dlls). 将MyAppComponents.wxs直接添加到您的主安装程序项目(让它用于2012)和任何其他项目“添加为链接”(让它为2013 dll)。 If you followed the instructions in step 5 then your build script is already made to compile against different versions of dependency assemblies. 如果您按照步骤5中的说明进行操作,那么您的构建脚本已经针对不同版本的依赖项程序集进行编译。 You want one source code compiled against two different dependencies right? 您想要针对两个不同的依赖项编译一个源代码吗? This way you have it - one .wxs which contains everything and inside the .cs files you have pragmas which guide the compilation. 这样你就拥有它 - 一个包含所有内容的.wxs,在.cs文件中你有编译引导编译。

Now when you add new file to your solution you will need to add it in your Mother project then "add as link" to your other projects and add the component registration in .wxs only in one place in order to have it distributed in all the installers. 现在,当您向解决方案中添加新文件时,您需要将其添加到您的Mother项目中,然后将“添加为链接”添加到您的其他项目中,并在.wxs中仅在一个地方添加组件注册,以便将其分发到所有项目中安装。

With the script from step 6 you will have all your versioned dlls inside one .wxs which mean they will be compiled in one single installer. 使用步骤6中的脚本,您将在一个.wxs中包含所有版本的dll,这意味着它们将在一个安装程序中编译。 Of course you can make several different installers for all your versions but it is simpler in the long run to make one single installer and to decide during installation which version of the .dll to copy to disk or to decide runtime which .dll to load in memory. 当然,你可以为你的所有版本制作几个不同的安装程序,但从长远来看,制作一个单独的安装程序并在安装过程中决定将哪个版本的.dll复制到磁盘或决定运行时加载哪个.dll更简单。记忆。 I chose to decide runtime and it works fine for me. 我选择决定运行时,它对我来说很好。

  1. After your two install projects area ready you can use TFS Build for integration. 准备好两个安装项目区域后,可以使用TFS Build进行集成。 You can go a step further and add an obfuscator like Smart Assembly in your build process so that at the end you have two or more separate installers with separately build dlls on different dependent libraries which contain obfuscated assemblies. 您可以更进一步,在构建过程中添加一个类似于Smart Assembly的混淆器,这样最终您就会有两个或多个单独的安装程序,这些安装程序在包含混淆程序集的不同依赖库上单独构建dll。

Everything described above is directly from trenches and it works. 上面描述的所有内容都直接来自战壕并且有效。 If I did not lay out all steps clearly just send me a message to clarify them and elaborate further. 如果我没有明确列出所有步骤,请给我发一条消息来澄清它们并进一步阐述。

I shifted away from Build Configurations because of the too much IFs and conditions in them that will accumulate with time when "yet another Autodesk dll appears". 我从构建配置转移了,因为它们中的IF和条件太多会随着“另一个Autodesk dll出现”而累积。 I am not very sure about this. 我对此不太确定。 Maybe it is a solution but you have to be very sure what you are doing in order not to brake the TFS Build if this is a requirement. 也许它是一个解决方案,但你必须非常确定你正在做什么,以便在不需要制动TFS Build时。

Your approach - of using multiple, separate resource-only projects (and assemblies) - is sound, and is typical for localized apps, and in other scenarios. 您的方法 - 使用多个单独的仅资源项目 (和程序集) - 是合理的,并且对于本地化应用程序和其他方案而言是典型的。 There are some namespace and scoping issues you need to be aware of - VS2005 always generates resource types as internal (or Friend in VB), while VS2008 allows you to vary that. 您需要注意一些命名空间和范围问题 - VS2005始终生成内部资源类型(或VB中的朋友),而VS2008允许您改变它。 You will have to do the right thing in order to be able to access multiple satellite assemblies from your main assembly -- scope the resource types publicly. 为了能够从主程序集访问多个附属程序集,您必须做正确的事情 - 公开调整资源类型的范围。

Once you have the main DLL, and the various resource DLLs, you have options for deployment. 一旦拥有主DLL和各种资源DLL,就可以选择部署。 One is to deploy the distinct DLLs separately, and load the proper one at runtime. 一种是分别部署不同的DLL,并在运行时加载正确的DLL。 Suppose your main EXE is called app.exe; 假设您的主要EXE称为app.exe; you would then also have Sat1.dll, sat2.dll, sat3.dll,... etc, for all of the satellite assemblies. 然后你会有所有卫星程序集的Sat1.dll,sat2.dll,sat3.dll等等。 Your setup project(s) would just include whatever DLLs are appropriate. 您的安装项目将包含任何适当的DLL。

The other option is to merge the DLLs, with ILMerge. 另一种选择是使用ILMerge合并DLL。 In this case you'd merge app.exe with sat1.dll, obtaining app1.exe. 在这种情况下,您将app.exe与sat1.dll合并,获取app1.exe。 Likewise app.exe+sat2.exe => app2.exe. 同样app.exe + sat2.exe => app2.exe。

To google around on this, use "localization" and "Satellite assemblies". 要了解这一点,请使用“本地化”和“卫星装配”。

Is what you are looking for multiple configurations? 您正在寻找多种配置? Take a look at: Build Configurations 看看: 构建配置

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM