Author Topic: Targeting Multiple AutoCAD Product Releases: Source Code Portability  (Read 15837 times)

0 Members and 1 Guest are viewing this topic.

TheMaster

  • Guest
While I won't get into the underlying reasons, I will say that the way that Autodesk restructured ObjectARX and its managed wrapper APIs serves the strategic objective of hindering release-independent source code portability and with that, the ability for developers to target multiple product releases with a single source code stream. In fact, they have pretty much made it nearly impossible for developers to continue to target AutoCAD 2012 and AutoCAD 2013 with the same source code.

Note that Kean Walmsly makes little mention of the fact that the changes in AutoCAD 2013's managed API break existing source code portability, and seems to trivialize that, something that most professional developers regard as a material 'breach of contract'. His article on the subject also seems to confuse 'binary compatibility' with source code compatibility/portability. The two are not the same. Binary compatibility is where the same build of a project can be run on different product releases. Source code portability is the ability to compile the same source code to multiple distributions targeting multiple product releases. While we have come to expect binary compatibility to be broken in each major release, we were not expecting source code to be broken to the extent that it has been broken in AutoCAD 2013, and with that, the ability to continue ongoing development of projects that target both AutoCAD 2010-2012 and 2013 has become more than most will be willing to bear.

This code is provided as an example showing one strategy for maximizing source code portability between AutocAD R18 and R19.  The basic strategy involves abstracting out differences between APIs in different releases, by exposing a version-independent wrapper API that determines which underlying native API must be called at runtime, and compiles a delegate that calls the appropriate API. Note that this approach uses compiled expressions to achieve the best performance, but a similar approach could be used that involved the use of standard Reflection for calling the underlying APIs.  The code is largely untested and you should use it at your own risk.

Code - C#: [Select]
  1. // Copyright (c) 2012  Tony Tanzillo
  2.  
  3. using System;
  4. using System.Reflection;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using Autodesk.AutoCAD.ApplicationServices;
  8.  
  9.  
  10. namespace namespace1
  11. {
  12.  
  13.   // Provides version-independent APIs that
  14.   // can run on AutoCAD R18 and R19.
  15.  
  16.   // Note that in order to achieve version-independence
  17.   // No types or namespaces that are unique to AutoCAD
  18.   // 2013 can be used directly from this code or the
  19.   // assembly containing it.
  20.  
  21.   // For that reason, this code should live in separate
  22.   // assemblies (one for each targeted release) and be
  23.   // referenced and used from other application code.
  24.  
  25.   public static class VersionInteropHelper
  26.   {
  27.  
  28.     // Document.GetAcadDocumentEx() Extension Method
  29.     //
  30.     // Returns the AcadDocument for the given Document.
  31.     //
  32.     // This extension method provides a version-independent
  33.     // way to obtain the AcadDocument object. Hence, it can
  34.     // be compiled against and run on any version of AutoCAD
  35.     // that uses .NET 3.5 or later (which is required for
  36.     // System.Linq.Expression)
  37.     //
  38.     // Usage:  
  39.     //
  40.     // Include this code in your project and use GetAcadDocumentEx()
  41.     // exclusively, in lieu of using Document.AcadDocument on AutoCAD
  42.     // 2012 and earlier, and DocumentExtension.GetAcadDocument() on
  43.     // AutoCAD 2013 or later.
  44.     //
  45.     // This code also serves as an example of a general pattern
  46.     // that can be applied to abstract out other breaking changes
  47.     // in source code between AutoCAD 2012 and AutoCAD 2013.
  48.  
  49.  
  50.     public static object GetAcadDocumentEx( this Document doc )
  51.     {
  52.       if( getAcadDocumentMethod == null )
  53.         getAcadDocumentMethod = CreateGetAcadDocumentMethod();
  54.       return getAcadDocumentMethod( doc );
  55.     }
  56.  
  57.     static Func<Document, object> getAcadDocumentMethod = null;
  58.  
  59.     static Func<Document, object> CreateGetAcadDocumentMethod()
  60.     {
  61.       Expression<Func<Document, object>> result = null;
  62.  
  63.       var parameters = new ParameterExpression[] {
  64.         Expression.Parameter( typeof( Document ), "document")
  65.       };
  66.  
  67.       if( AcadVersion.Major > 18 )
  68.       {
  69.         // AcMgd.dll:
  70.         Type type = typeof( Autodesk.AutoCAD.Windows.PaletteSet )
  71.           .Assembly.GetType( "Autodesk.AutoCAD.ApplicationServices.DocumentExtension", true );
  72.  
  73.         MethodInfo mi = type.GetMethod( "GetAcadDocument",
  74.           BindingFlags.Public | BindingFlags.Static
  75.         );
  76.  
  77.         result = Expression.Lambda<Func<Document, object>>(  
  78.           Expression.Call( mi, parameters ),
  79.           parameters
  80.         );
  81.       }
  82.       else
  83.       {
  84.  
  85.         MethodInfo method = typeof(Document).GetProperty("AcadDocument").GetGetMethod();
  86.         result = Expression.Lambda<Func<Document, object>>(  
  87.           Expression.Call( parameters[0], method ),
  88.           parameters[0]
  89.         );
  90.       }
  91.  
  92.       return result.Compile();
  93.     }
  94.  
  95.     // Let's all give a big round of applause to Autodesk for
  96.     // removing the Version property from the Application object
  97.     // in AcMgd.dll.  In order for version-independent code to
  98.     // branch to different execution paths depending on whether
  99.     // it is running on R18 or R19, it must check the version of
  100.     // the application that it's running on at runtime.
  101.     //
  102.     // But, because they did not leave the version property on the
  103.     // Application object in AcMgd.dll, there is no direct version-
  104.     // independent way to get the application version... LMAO
  105.  
  106.     // A version-independent way to get the AutoCAD version at runtime (LOL)
  107.  
  108.     public static Version AcadVersion
  109.     {
  110.       get
  111.       {
  112.         return typeof( Document ).Assembly.GetName().Version;
  113.       }
  114.     }
  115.  
  116.   }
  117. }
  118.  
« Last Edit: June 04, 2012, 01:00:51 AM by TheMaster »

TheMaster

  • Guest
After examining the AutoCAD 2013 migration quagmire further, I've concluded that the above strategy and code is useful mainly when there is binary compatibility between the supported product releases (e.g., where the same build can be loaded into all supported AutoCAD versions).

However, since that is not the case between AutoCAD 2013 and previous releases, here is a far better solution to the problem of maximizing application source code portability between AutoCAD 2013 and AutoCAD 2012 and earlier. The most compelling aspect of this solution is that it completely eliminates the need for release-dependent code and the messy conditional compilation directives (#if/#else/#endif) needed to include/exclude release-specific code depending on the target AutoCAD release.

Code - C#: [Select]
  1.  
  2. // Copyright (c) 2012  Tony Tanzillo
  3.  
  4. using System;
  5. using Autodesk.AutoCAD.ApplicationServices;
  6. using Autodesk.AutoCAD.Windows;
  7. using System.Drawing;
  8.  
  9. // APIs that provide support for elimiation of release-
  10. // dependent code in projects targeting multiple AutoCAD
  11. // product releases.
  12.  
  13. // This code is designed to maximize application source
  14. // code portability between AutoCAD 2013 and AutoCAD
  15. // 2012 and earlier. By using this code in projects that
  16. // target AutoCAD 2012 or earlier, you can avoid the need
  17. // for extensive amounts of additional release-specific
  18. // code, and associated use of conditional compilation
  19. // directives (#if/#else/#endif).
  20. //
  21. // These extension methods are identical to the extension
  22. // methods introduced in AutoCAD 2013, that replace what
  23. // was previously exposed through properties in AutoCAD
  24. // 2012 and earlier.
  25. //
  26. // You can use this code in projects targeting AutoCAD 2012
  27. // or earlier, to allow the same calling code to run on both
  28. // AutoCAD 2012 and AutoCAD 2013. Once you've added this code
  29. // to your projects targeting AutoCAD 2012 or earlier, you
  30. // should replace all references to the 'defunct' properties
  31. // which the included extension methods replace with calls to
  32. // the included extension methods. Once you've done that, any
  33. // code that uses these extension methods can be compiled and
  34. // run on AutoCAD 2013 and 2012 or earlier.
  35. //
  36. // The affected AutoCAD 2012 API properties are:
  37. //
  38. //   Document.AcadDocument
  39. //   Document.StatusBar
  40. //
  41. //   Window.Size
  42. //   Window.Location
  43. //   Window.Icon
  44. //
  45. // You should only include this code in projects targeting
  46. // AutoCAD 2012 or earlier, as the included extension methods
  47. // are provided by AutoCAD's managed API on AutoCAD 2013.
  48. //
  49. // Note that these APIs do not eliminate the need to revise
  50. // source code to run on AutoCAD 2013. What these APIs do is
  51. // allows source code that's been revised to run on AutoCAD
  52. // 2013 to also run on AutoCAD 2012.
  53.  
  54. namespace Autodesk.AutoCAD.ApplicationServices
  55. {
  56.   public static class DocumentExtension
  57.   {
  58.     public static object GetAcadDocument( this Document doc )
  59.     {
  60.       return doc.AcadDocument;
  61.     }
  62.  
  63.     public static StatusBar GetStatusBar( this Document doc )
  64.     {
  65.       return doc.StatusBar;
  66.     }
  67.   }
  68. }
  69.  
  70. namespace Autodesk.AutoCAD.Windows
  71. {
  72.   public static class WindowExtension
  73.   {
  74.     public static Size GetSize( this Window window )
  75.     {
  76.       return window.Size;
  77.     }
  78.    
  79.     public static void SetSize( this Window window, Size value )
  80.     {
  81.       window.Size = value;
  82.     }
  83.    
  84.     public static Icon GetIcon( this Window window )
  85.     {
  86.       return window.Icon;
  87.     }
  88.    
  89.     public static void SetIcon( this Window window, Icon value )
  90.     {
  91.       window.Icon = value;
  92.     }
  93.    
  94.     public static Point GetLocation( this Window window )
  95.     {
  96.       return window.Location;
  97.     }
  98.    
  99.     public static void SetLocation( this Window window, Point value )
  100.     {
  101.       window.Location = value;
  102.     }
  103.   }
  104. }
  105.  
  106.  

[edit]  Corrected erronous 'AcadObject' with 'AcadDocument'
« Last Edit: June 04, 2012, 03:36:08 PM by TheMaster »

TheMaster

  • Guest

[edit]  Corrected erronous 'AcadObject' with 'AcadDocument'


Oops again - 'AcadObject' -> 'AcadDocument' corrected.

GTVic

  • Guest
Hi, I tried this with VB.NET but it doesn't allow "static" and seemed to replace the entire namespace so other classes were not available.

I tried reflection and was successful for getting the Document object on pre-2013 but not the 2013 which seemed to require a query on all assemblies which took a few seconds so wasn't worth it.

Reflection:

Code: [Select]
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.Windows
Imports System.Reflection
Imports System.Drawing
Imports System.Linq
Imports System

Public Class VersionCode

    ' these all work
    Public Function GetTextStyleId(ByVal Instance As DBText) As ObjectId
        If textStyleProperty IsNot Nothing Then
            Return DirectCast(textStyleProperty.GetValue(Instance, Nothing), ObjectId)
        Else
            Throw New MissingMemberException()
        End If
    End Function

    Public Function GetTextStyleId(ByVal Instance As MText) As ObjectId
        If mtextStyleProperty IsNot Nothing Then
            Return DirectCast(mtextStyleProperty.GetValue(Instance, Nothing), ObjectId)
        Else
            Throw New MissingMemberException()
        End If
    End Function

    Public Sub SetTextStyleId(ByVal Instance As DBText, ByVal value As ObjectId)
        If textStyleProperty IsNot Nothing Then
            textStyleProperty.SetValue(instance, value, Nothing)
        Else
            Throw New MissingMemberException()
        End If
    End Sub

    Public Sub SetTextStyleId(ByVal Instance As MText, ByVal value As ObjectId)
        If mtextStyleProperty IsNot Nothing Then
            mtextStyleProperty.SetValue(instance, value, Nothing)
        Else
            Throw New MissingMemberException()
        End If
    End Sub

    Public Sub New()
        textStyleProperty = GetType(DBText).GetProperty("TextStyle")
        If textStyleProperty Is Nothing Then
            textStyleProperty = GetType(DBText).GetProperty("TextStyleId")
        End If
        mtextStyleProperty = GetType(MText).GetProperty("TextStyle")
        If mtextStyleProperty Is Nothing Then
            mtextStyleProperty = GetType(MText).GetProperty("TextStyleId")
        End If

        ' this part may be working but the associated code in GetAcadDocument doesn't and the query takes way too long
        Dim ns As String = "Autodesk.AutoCAD.ApplicationServices.DocumentExtension"
        Dim namespaceFound As Object

        namespaceFound = (From assembly In AppDomain.CurrentDomain.GetAssemblies() From type In assembly.GetTypes() Where type.Namespace = ns Select type).Any()
        If namespaceFound IsNot Nothing Then
            DocumentExtensionClass = Activator.CreateInstance(namespaceFound)
        End If
        If DocumentExtensionClass IsNot Nothing Then
            GetAcadDocumentMethod = DocumentExtensionClass.GetMethod("GetAcadDocument")
        End If

        ' this works for pre-2013
        AcadDocumentProperty = GetType(Autodesk.AutoCAD.ApplicationServices.Document).GetProperty("AcadDocument")

    End Sub

    Public Function GetAcadDocument(ByVal WhichDocument As Document) As Object
        GetAcadDocument = Nothing
        ' this doesn't work I think I have to replace Invoke(Nothing, params) with Invoke(DocumentExtensionClass, params)
        ' but I abandoned this before testing because the query took so long
        If GetAcadDocumentMethod IsNot Nothing Then
            Dim params(0 To 0) As Object
            params(0) = WhichDocument
            Return DirectCast(GetAcadDocumentMethod.Invoke(Nothing, params), Object)
        End If
        ' this works for pre-2013
        If AcadDocumentProperty IsNot Nothing Then
            Return DirectCast(AcadDocumentProperty.GetValue(WhichDocument, Nothing), Object)
        End If
    End Function

    Private textStyleProperty As PropertyInfo = Nothing
    Private mtextStyleProperty As PropertyInfo = Nothing

    Private DocumentExtensionClass As Type = Nothing
    Private GetAcadDocumentMethod As MethodInfo = Nothing
    Private AcadDocumentProperty As PropertyInfo = Nothing

End Class

Namespace Method

Code: [Select]
Namespace Autodesk.AutoCAD.ApplicationServices
    Public Class DocumentExtension
        Public Function GetAcadDocument(ByRef doc As Document) As Object
            Return doc.AcadDocument
        End Function
        Public Function GetStatusBar(ByRef doc As Document) As Object
            Return doc.StatusBar
        End Function
    End Class
End Namespace

Namespace Autodesk.AutoCAD.Windows
    Public Class WindowExtension
        Public Function GetSize(ByVal win As Window) As Size
            Return win.Size
        End Function
        Public Sub SetSize(ByVal win As Window, ByVal value As Size)
            win.Size = value
        End Sub
        Public Function GetIcon(ByVal win As Window) As Icon
            Return win.Icon
        End Function
        Public Sub SetIcon(ByVal win As Window, ByVal value As Icon)
            win.Icon = value
        End Sub
        Public Function GetLocation(ByVal win As Window) As Point
            Return win.Location
        End Function
        Public Sub SetLocation(ByVal win As Window, ByVal value As Point)
            win.Location = value
        End Sub
    End Class
End Namespace

Jeff H

  • Needs a day job
  • Posts: 6144
Quote
Extension methods can be declared only within modules

Quote

 extension methods must be marked with the extension attribute <Extension()> from the System.Runtime.CompilerServices namespace.
MSDN
 

TheMaster

  • Guest
Hi, I tried this with VB.NET but it doesn't allow "static" and seemed to replace the entire namespace so other classes were not available.

There is no 'static' in VB.NET. The VB.NET equivalent is 'Shared'.


GTVic

  • Guest
Requires .NET 3.5 to use <Extension()> in VB.NET but you cannot add an extension class to an existing namespace so this seems to need a different approach.

I created a new module for my 2009-2012 project with the following code and do not use this module in my 2013 project.

Code: [Select]
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.Windows
Imports System.Drawing

Public Module DocumentExtension
    Function GetAcadDocument(ByRef doc As Document) As Object
        Return doc.AcadDocument
    End Function
    Function GetStatusBar(ByRef doc As Document) As Object
        Return doc.StatusBar
    End Function
End Module

Public Module WindowExtension
    Function GetSize(ByVal win As Window) As Size
        Return win.Size
    End Function
    Sub SetSize(ByVal win As Window, ByVal value As Size)
        win.Size = value
    End Sub
    Function GetIcon(ByVal win As Window) As Icon
        Return win.Icon
    End Function
    Sub SetIcon(ByVal win As Window, ByVal value As Icon)
        win.Icon = value
    End Sub
    Function GetLocation(ByVal win As Window) As Point
        Return win.Location
    End Function
    Sub SetLocation(ByVal win As Window, ByVal value As Point)
        win.Location = value
    End Sub
End Module

CADbloke

  • Bull Frog
  • Posts: 342
  • Crash Test Dummy
A few questions and solicitations for opinions ...

1. Is there any document that lists all the breaking changes and new bits added to the AutoCAD .NET API over the years? I know about the "What's New" section in the arxmgd.chm files for each year but is this collated in one place?

2. Versions: where do you draw the line, given that .NET is supported in v2006, v2009 changed quite a few things and v2012 was the first to support .NET 4.0 ? Me, I've currently added v2007 & 8 to the mix but I'm tempted to drop them because of missing parts in the API and also because customers using versions that old are more likely to be Freetards or Pathological customers, best avoided and/or dealt with using a long stick.  My gut-feeling is start with v2010. Yours?

3. Further to the missing bits (properties, methods etc) I am using extension methods to fill the holes as per TT's ideas and code here. This is not a question.

4. How do you set up Projects / Solutions to cater for different builds?

I have butchered a Visual Studio template .csproj file (and a .csproj.user file which must be added later) to use conditionals and wildcards to set up different builds with different Library and .NET framework references. It's a little fragile because Visual Studio likes to fill in the Wildcards when it builds the Template into a Project so I also leave the original wildcards in comments so I can re-butcher them. Source Control is a must if you try this.

4a. Now that I have mentioned that Visual Studio  template I really should tidy it up (that should be interesting, TT knows what I'm talking about here) and post it here if anyone is interested. Interested?

TheMaster

  • Guest
I don't remember the thread, but I described one of the ways I dealt with projects that target multiple releases, which is to store release-specific source files in different folders along with a 'common' folder that is used in all releases, and then build separate projects that add those files as links (so they're not copied to the project folder).

The threads mentioned here have most of what I would repeat here.  There are some new things in later versions of .NET (like Linq.Expression) that allows you to avoid the overhead of reflection when you have two incompatible APIs, but I've yet to resort to that.

I don't know of any document that describes the differences between API releases, and in that regards, you're kind of on your own, but doing a diff on two different releases of the same assembly will show them.

   http://www.hanselman.com/blog/ManagingChangeWithNETAssemblyDiffTools.aspx


Jeff H

  • Needs a day job
  • Posts: 6144
Does anyone use partial classes and/or methods to "Link in" "common or shared" portions of classes?

CADbloke

  • Bull Frog
  • Posts: 342
  • Crash Test Dummy
Re: Targeting Multiple AutoCAD Product Releases: Source Code Portability
« Reply #10 on: April 30, 2013, 06:38:32 AM »
====== EDIT October 2, 2015 ====

I'm having another crack at this at https://github.com/CADbloke/CodeCloner

This time around I'm focusing on propagating source code to destination Projects which control all their own build settings. Apparently some (all) of us though that 72 builds in one project was a little unmanageable </YesItWas>.

There's an extensive readme there so I won't duplicate it here. I'd love to hear any thoughts, suggestions, WTFs.
My earlier works left here to serve as a warning for others..and also because there are a couple of useful tips in there.
...

My methodology (as of writing - this can change without notice) for supporting different versions involves using one project with different builds. One of my project actually has 36 builds, all using the same code. Well, it seemed like a good idea at the time.

A Visual Studio project file is just an XML file, so don't be shy about editing it by hand. Here is a the MSDN landing page for more info

I need to massage my Visual Studio project template a fair bit for public consumption, add a wizard to fix a few hard-coded fragilities  and also write up an explanation for what it does and why. Here's a teaser in the meantime, just a few excerpts for the 2007 version. I also have builds for 2013, 2012 (both build on .NET 4.0) & 2010 (v3.5) ...

Code - XML: [Select]
  1. <!-- Version 2007 Builds -->
  2. <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug Acad2007|AnyCPU' ">
  3.  <DefineConstants>TRACE;DEBUG;ACAD2007</DefineConstants>
  4.         <AssemblyName>$projectname$Acad2007</AssemblyName>
  5.                          <OutputPath>bin\Debug\ACAD2007\</OutputPath>
  6.         <DocumentationFile>bin\Debug\ACAD2007\$projectname$Acad2007.XML</DocumentationFile>
  7. </PropertyGroup>
  8. <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Acad2007|AnyCPU' ">
  9. <DefineConstants>TRACE;RELEASE;ACAD2007</DefineConstants>
  10.         <AssemblyName>$projectname$Acad2007</AssemblyName>
  11.                         <OutputPath>bin\Release\ACAD2007\</OutputPath>
  12. </PropertyGroup>
  13.  
  14. ......
  15.  
  16. <!--                    2007 version. -->
  17. <PropertyGroup Condition=" $(DefineConstants.Contains('2007')) ">
  18. <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
  19.         </PropertyGroup>
  20.   <ItemGroup Condition=" $(DefineConstants.Contains('2007')) " >    
  21.     <Reference Include="AcDbMgd">
  22.       <HintPath>$CadLibraryRootFolder$\2007\AcDbMgd.dll</HintPath>
  23.       <Private>False</Private>
  24.     </Reference>
  25.     <Reference Include="AcMgd">
  26.       <HintPath>$CadLibraryRootFolder$\2007\AcMgd.dll</HintPath>
  27.       <Private>False</Private>
  28.     </Reference>
  29. <!-- BCL references depend on the .NET Framework you are using -->
  30.                 <Reference Include="PresentationCore" />
  31.                 <Reference Include="PresentationFramework" />
  32.                 <Reference Include="System" />
  33.                 <Reference Include="System.Data" />
  34.                 <Reference Include="System.Data.Linq" />
  35.                 <Reference Include="System.Drawing" />
  36.                 <Reference Include="System.Windows.Forms" />
  37.                 <Reference Include="System.Xml" />
  38.                 <Reference Include="WindowsBase" />
  39.                 <Reference Include="WindowsFormsIntegration" />
  40.         </ItemGroup>
  41. <!-- TODO: You will probably need to restore some wildcard entris here as per the comments -->
  42. <!-- If you need to restore wildcard links to other source files then see comments below  ...
  43.    <CustomParameter Name="$CadExtensionClassesCommonFolder$" Value="Z:\z.Libraries\AutoCAD\ExtensionClasses\Common"/>  
  44.    <CustomParameter Name="$CadExtensionClassesOldVersionsFolder$" Value="z:\z.Libraries\AutoCAD\ExtensionClasses\Extensions for Old Versions"/>  
  45. -->
  46.         <ItemGroup>
  47.                 <Compile Include="$CadExtensionClassesCommonFolder$\*.*" >
  48.                         <Link>Extensions\common\%(RecursiveDir)%(Filename)%(Extension)</Link>
  49. <!--    <Compile Include="$CadExtensionClassesCommonFolder$\*.*" >
  50.            <Link>Extensions\common\%(RecursiveDir)%(Filename)%(Extension)</Link>-->
  51.                 </Compile>
  52.                 <Compile Condition=" $(DefineConstants.Contains('2012')) Or $(DefineConstants.Contains('2010')) Or $(DefineConstants.Contains('2007')) "
  53.               Include="$CadExtensionClassesOldVersionsFolder$\*.*" >  
  54.                         <Link>Extensions\For Old Versions\%(RecursiveDir)%(Filename)%(Extension)</Link>
  55. <!--                    <Compile Condition=" $(DefineConstants.Contains('2012')) Or $(DefineConstants.Contains('2010')) Or $(DefineConstants.Contains('2007')) "
  56.               Include="$CadExtensionClassesOldVersionsFolder$\*.*" >  
  57.                         <Link>Extensions\For Old Versions\%(RecursiveDir)%(Filename)%(Extension)</Link>
  58. -->
  59.                 </Compile>
  60.                 <Compile Include="AppInitializeTerminate.cs" />
  61.                 <Compile Include="Command.cs" />
  62.                 <Compile Include="Properties\AssemblyInfo.cs" />
  63.                 <None Include="ReadMe.txt" />
  64.                 <None Include="NetLoadScripts\*.*" />
  65.         </ItemGroup>

and I drop this into my .csproj.user file ...

Code - XML: [Select]
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3.   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug Acad2013|AnyCPU' ">
  4.     <StartAction>Program</StartAction>
  5.     <StartProgram>C:\Program Files\Autodesk\AutoCAD 2013\acad.exe</StartProgram>
  6.     <StartArguments>/b C:\Codez\CADbloke\CADtools\$projectname$\$projectname$\NetLoadScripts\AutoNetLoadDebug2013.scr"</StartArguments>
  7.   </PropertyGroup>
  8.  
  9.   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Acad2013|AnyCPU' ">
  10.     <StartAction>Program</StartAction>
  11.     <StartProgram>C:\Program Files\Autodesk\AutoCAD 2013\acad.exe</StartProgram>
  12.     <StartArguments>/b C:\Codez\CADbloke\CADtools\$projectname$\$projectname$\NetLoadScripts\AutoNetLoadRelease2013.scr"</StartArguments>
  13.   </PropertyGroup>
  14. </Project>
  15.  

The whole .csproj file is >410 lines. There is also a .vstemplate file that defines all the parameters. There's also a batch file that builds the project template and installs it in the Visual Studio folder, plus a template for the Command.cs file.

The XML comments are there because the Visual Studio project generator tends to strip out wildcards and just make a list of what is there at the time. I prefer the wildcards so I don't have to fix every project in existence if I add, remove or rename a file in the linked folders.

Z: is a mapped drive because the VS project maker changes the links to relative ones. That's a bit fragile for my liking - if you move the project all the links break. I haven't tried it with an environment variable yet :: Edit, yes I have and it works ::: which would a easier to manage than mapping a duplicate drive.

ReSharper will complain when you switch between pre-2013 & 2013-onwards builds because of the extra DLL (AcCoreMgd.dll). Also, the observant among you probably want to tell me that v2007 is x86-only so an "Any CPU" build will generate build warnings. Yeah, I know. It's on the list.

There's more to it than just this but I hope it gives you an idea of how this alternative works.

Edit: I have since broken the monolithic csproj file into several smaller ones, based on what is unchanged between projects. I then import them into the specific project I am working on. The bonus is that Visual Studio doesn't seem to alter imported files so I don't lose any of the wildcard links.

The order you import them is important, if there are any cascading dependencies then they need to be declared before you can use them.

============================== EDIT March 3rd, 2015 =================

I don't do it this way any more, well not as confusingly. Perhaps. These days I have 2 CSPROJ files I import for each version of AutoCAD (NanoCAD, BricsCAD etc. too), the first file I import defines the build configuration name, sets the compilation constants (eg. ACAD2015) and sets the output path and sets the Debug settings. The second file I import sets the project references based on the Compilation constants. The main project file has no output paths or AutoCAD References.  Here is an example...
Main Project file (the first part of it)
Code - XML: [Select]
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3.   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  4.   <!-- Constants are important because the Reference imports are dependent on them -->
  5.   <PropertyGroup>
  6.     <DefineConstants>$(DefineConstants);TEST;CoreConsole</DefineConstants>
  7.   </PropertyGroup>
  8.   <!-- Import order is important here - Need to define the build constants before Setting conditional references -->
  9.   <Import Project="$(Codez)\z.Libraries\AutoCAD\VisualStudioCSPROJ\AutoCAD2015-20.0-DebugRelease.csproj" />
  10.   <Import Project="$(Codez)\z.Libraries\AutoCAD\VisualStudioCSPROJ\AutoCAD2015-20.0-References.csproj" />
  11.   <Import Project="$(Codez)\z.Libraries\AutoCAD\VisualStudioCSPROJ\AutoCAD2014-19.1-DebugRelease.csproj" />
  12.   <Import Project="$(Codez)\z.Libraries\AutoCAD\VisualStudioCSPROJ\AutoCAD2014-19.1-References.csproj" />
  13.   <Import Project="$(Codez)\z.Libraries\AutoCAD\VisualStudioCSPROJ\AutoCAD2013-19.0-DebugRelease.csproj" />
  14.   <Import Project="$(Codez)\z.Libraries\AutoCAD\VisualStudioCSPROJ\AutoCAD2013-19.0-References.csproj" />
  15.  
  16. ...
  17.     <Reference Include="WindowsBase" Condition="!$(DefineConstants.Contains('NET35'))" />
  18. ...
  19.  

AutoCAD2015-20.0-DebugRelease.csproj
Code - XML: [Select]
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3. <!-- IMPORTANT: Project reference CSPROJ must be imported after this because this defines the conditional constants -->
  4. <!-- Debug &  Release versions for 2015 -->
  5.   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug Acad2015|AnyCPU'">
  6.     <DefineConstants>$(DefineConstants);TRACE;DEBUG;ACAD;AutoCAD;ACAD2015;AutoCAD2015;NET45</DefineConstants>
  7.     <OutputPath>bin\Debug\ACAD2015\</OutputPath>
  8.     <DocumentationFile>$(OutputPath)\$(MSBuildProjectName).xml</DocumentationFile>
  9.   </PropertyGroup>
  10.  
  11.   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release Acad2015|AnyCPU'">
  12.     <DefineConstants>$(DefineConstants);RELEASE;ACAD;AutoCAD;ACAD2015;AutoCAD2015;NET45</DefineConstants>
  13.     <OutputPath>bin\Release\ACAD2015\</OutputPath>
  14.   </PropertyGroup>
  15.         <!-- These scripts are generated by the VS Project template or copy from the AutoCAD Libs folder.
  16. If the $SolutionName$ or $projectname$ changes then you will need to Grep them to update the DLL names  -->
  17.         <PropertyGroup Condition="$(DefineConstants.Contains('ACAD2015')) ">
  18.                 <StartAction>Program</StartAction>
  19.                 <StartProgram>C:\Program Files\Autodesk\AutoCAD 2015\acad.exe</StartProgram>
  20.         </PropertyGroup>
  21.         <!-- AnyCPU -->
  22.         <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug Acad2015|AnyCPU'">
  23.                 <StartArguments>/b "$(MSBuildProjectDirectory)\NetLoadScripts\AutoNetLoadDebug2015.scr"</StartArguments>
  24.         </PropertyGroup>
  25.         <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release Acad2015|AnyCPU'">
  26.                 <StartArguments>/b "$(MSBuildProjectDirectory)\NetLoadScripts\AutoNetLoadRelease2015.scr"</StartArguments>
  27.         </PropertyGroup>
  28. </Project>

AutoCAD2015-20.0-References.csproj
Code - XML: [Select]
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3.         <!-- Project References for AutoCAD 2015 Visual Studio Projects -->
  4.         <!-- IMPORTANT: Build config CSPROJ must be imported before this because it defines the conditional constants -->
  5.         <PropertyGroup Condition=" $(DefineConstants.Contains('ACAD2015')) ">
  6.                 <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
  7.                 <PlatformTarget>AnyCPU</PlatformTarget>
  8.         </PropertyGroup>
  9.  
  10.         <!-- NO tests -->
  11.         <ItemGroup Condition=" $(DefineConstants.Contains('ACAD2015')) And (!$(ProjectName.EndsWith('Tests')) Or $(ProjectName.EndsWith('CADTests')) Or !$(DefineConstants.Contains('CADTest'))) ">
  12.                 <Reference Include="AcMgd" Condition="!$(DefineConstants.Contains('CoreConsole')) And !$(DefineConstants.Contains('CORECONSOLE'))">
  13.                         <HintPath>$(Codez)\z.Libraries\AutoCAD\2015\AcMgd.dll</HintPath>
  14.                         <Private>False</Private>
  15.                 </Reference>
  16.                 <Reference Include="AcCoreMgd">
  17.                         <HintPath>$(Codez)\z.Libraries\AutoCAD\2015\AcCoreMgd.dll</HintPath>
  18.                         <Private>False</Private>
  19.                 </Reference>
  20.                 <Reference Include="AcDbMgd">
  21.                         <HintPath>$(Codez)\z.Libraries\AutoCAD\2015\AcDbMgd.dll</HintPath>
  22.                         <Private>False</Private>
  23.                 </Reference>
  24.                 <Reference Include="AdUiPalettes"  Condition="!$(DefineConstants.Contains('CoreConsole')) And !$(DefineConstants.Contains('CORECONSOLE'))">
  25.                         <HintPath>$(Codez)\z.Libraries\AutoCAD\2015\inc-x64\AdUiPalettes.dll</HintPath>
  26.                         <Private>False</Private>
  27.                 </Reference>
  28.         </ItemGroup>
  29.  
  30.         <!-- WITH tests -->
  31.         <ItemGroup Condition=" $(DefineConstants.Contains('ACAD2015')) And $(ProjectName.EndsWith('Tests')) And (!$(ProjectName.EndsWith('CADTests')) Or !$(DefineConstants.Contains('CADTest'))) ">
  32.                 <!-- WITH tests -->
  33.                 <Reference Include="AcMgd" Condition="!$(DefineConstants.Contains('CoreConsole')) And !$(DefineConstants.Contains('CORECONSOLE'))">
  34.                         <HintPath>$(Codez)\z.Libraries\AutoCAD\2015\AcMgd.dll</HintPath>
  35.                         <Private>True</Private>
  36.                 </Reference>
  37.                 <Reference Include="AcCoreMgd">
  38.                         <HintPath>$(Codez)\z.Libraries\AutoCAD\2015\AcCoreMgd.dll</HintPath>
  39.                         <Private>True</Private>
  40.                 </Reference>
  41.                 <Reference Include="AcDbMgd">
  42.                         <HintPath>$(Codez)\z.Libraries\AutoCAD\2015\AcDbMgd.dll</HintPath>
  43.                         <Private>True</Private>
  44.                 </Reference>
  45.                 <Reference Include="AdUiPalettes"  Condition="!$(DefineConstants.Contains('CoreConsole')) And !$(DefineConstants.Contains('CORECONSOLE'))">
  46.                         <HintPath>$(Codez)\z.Libraries\AutoCAD\2015\inc-x64\AdUiPalettes.dll</HintPath>
  47.                         <Private>True</Private>
  48.                 </Reference>
  49.         </ItemGroup>
  50. </Project>

A Visual Studio project file is just an XML file, so don't be shy about editing it by hand. Here is a the MSDN landing page for more info. Yes, I already said this at the top but that's actually the "easy" way to do this. If you are using Notepad++ then try this Syntax Highlighter.
« Last Edit: October 02, 2015, 07:59:48 AM by CADbloke »

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
I just saw this thread, and noticed the mention of using partial classes.
That approach is what I have done so far, and I don't really like it.
Code in two files for same class is annoying.
You end up with a separate partial code file for each release, and also a separate project that assembles them.
Then different references in the main project that uses the helper libraries.

Looks like:
James Maeding

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Targeting Multiple AutoCAD Product Releases: Source Code Portability
« Reply #12 on: March 05, 2015, 05:28:10 AM »
This is maybe interesting: the book about the MSBuild.