When I started the ‘Colibra’ project, I had two goals in my mind: eliminate complexity by providing an interface that will suit the needs of the applications I am writing, and encapsulate external dependencies to eliminate duplicate code and have a single point of maintenance when things change in the AutoCAD Civil 3D API.
Despite how straight forward these two goal seem, the reality is that we tend to take shortcuts when we write code and not always think through whether the implementation meets these requirements. But these requirements are very important. If there is one thing that we can be certain about the future, it is that there will be changes, and when those changes arrive, we want to be prepared.
Before I became part of the Civil 3D API team, I was a consumer of the Civil 3D API, very much like you. Back then, I was not that careful when I wrote code using the Civil 3D API. Working on Autodesk projects, I knew my code will always run together with Civil 3D, and I thought it was OK to slip one dependency or another. That is, until I started maintaining the projects. You know, the phase when you already shipped your application and a new release of Civil 3D becomes available and you want to support it.
With every release of Civil 3D there are changes to the API. There might be improvements to existing API’s you want to leverage, but this is not always easy if the code you need to change is wide spread throughout your application.
You may also find that features that were only available through the COM API are now available in the .NET API. You want to use the .NET API, but you have so many dependencies in your code related to COM, and the object models are so different that it is very hard to port the code safely.
You also see there are new features available, but using them create additional dependencies that you want to avoid. You want to overcome the difficulties, but you do not know how.
These are problems that we all, as developers, have experienced in the past. And we know, from that experience, that there is a solution to them; it just takes a little bit of care, and some analysis and thought before we write the code. The key to solve these issues is that you set the same goals I did for ‘Colibra’ in your own projects. That is, provide interfaces specific to your needs to reduce complexity and encapsulate external dependencies to eliminate repetitive code and provide a single point of maintenance.
Interfaces
When we talk about interfaces many things come to mind, so I want to explain what I am talking about. The public properties and methods of a class compose the interface of that class. This differs from the more recent concept of interface definitions, which are the specification of properties and methods that need to be implemented in a class to support a specific interface. Interface definitions are nice because they let us write our programs targeting them, as opposed to a specific implementation. They allow to have many implementations providing a more modern way of polymorphism (change of behavior during execution).
But when I talk about providing specific interfaces for your application, I am referring to the properties and methods you invoke in the classes that you implement. You can still have interface definitions that will allow you to have different implementations of the same interface, but the goal is to work with a specific interface that is related to the functionality of your application. The implementation hides the complexity of using lower-level objects provided by external API’s and centralizes the location of the code that accesses those API”s, eliminating code duplication.
Dependencies
Dependencies are known to be the source of most of the issues found in object oriented software these days. Most literature about object oriented programming and design defines principles that basically try to remove dependencies between objects. Design patterns, language idioms, and new features provided in modern languages try to reduce the number of dependencies introduced when we are writing software. Basically, the more dependencies you introduce in your code, the more difficult it is to change and maintain.
When you expose a type in the public interface of one of your classes, you are not only introducing a dependency in your class, but you are introducing a dependency on all client code that uses this class. If this type is provided by a external library or API, you are now forcing a dependency to this library on all the client code. If this type changes, not only you will have to change the implementation of your class, but also all the client code that uses it. This is especially bad because you do not have any control on how or when an external API is going to change.
You always want to contain external libraries in especial containers called classes. These classes will provide the required interface to your application. When things change, you will have to fix the implementation in your classes, which you have absolute control to do, but you will not have to fix any of the client code making the changes centralized and easier to spot and test.
Programming to interfaces and encapsulating dependencies are the way to success in software development. They allow to centralize points of change and eliminate code duplication. In subsequent posts, I will be applying these concepts while implementing the ‘Colibra’ library, starting with the implementation of a document manager for AutoCAD Civil 3D. Managing documents seems to be a source of pain and code duplication that I hope to help everyone resolve in a way that will serve you now and in the future, no matter what changes in the API.
Also, would it be safe, then, to consider "interface definition" different from interface by using "prototype" as a term for that which is not part of a classes public methods and properties? I seem to recall Delphi using terminology like this for virtual classes and other I-types.
Posted by: Chris | 03/21/2011 at 02:14 PM
Private properties and methods not exposed, are usually referred as implementation methods and properties. I have never seen them referred as prototypes, but I have not use Delphi extensively.
Posted by: Isaac Rodriguez | 03/22/2011 at 07:00 AM
Hi, Mike
I have downloaded the sample code, and when I load the TinyRunner.dll, and I input the command described in the class of TinyRunnerCommands, I found that nothing happened. Then I set a break-point to check that whether the application go into the command-function, the result is that nothing happened.
Am I forgetting something?
Thank you for reading this question.
Posted by: Tomans | 04/12/2011 at 09:02 PM
Tomans,
Let me see if I understood you correctly. You use the 'NETLOAD' command to load TinyRunner.dll, and when you type the commands registered there, they are not being called?
What version of Visual Studio are you using?
Posted by: Isaac Rodriguez | 04/13/2011 at 06:16 AM
Yes, exactly as you said. Sorry for not describing very clearly. :)
I am using the Visual Studio 2010 right now.
So what should I do?
Thank you very much.
Posted by: Tomans | 04/13/2011 at 06:16 PM
Tomans,
A couple of things to check. First, the provided code has been implemented with VS2008. If you have VS2008 available, I will try that first.
If you are using VS2010, I am assuming the solution and project files have been upgraded when you first loaded them in VS2010. There is chance that by default, VS2010 has modified the target framework of .NET 4.0. If you are trying to run the code in Civil 3D 2011, which I am assuming you are unless you are testing a Beta version of Civil 3D 2012, the DLL targeting .NET 4.0 won't load.
If this is the case, you have to go to the properties of every project in the solution and change the .NET framework target to .NET 3.5 (not .NET 3.5 Client Profile). This is done at the 'Application' tab of the project properties, where you will see a 'Target Framework' dropdown list. After you change the target for all the projects, you need to rebuild the solution and the resulting DLLs will be targeting the correct framework supported by Civil 3D 2011.
Let me know if that helps,
P.S. My latest post talks about setting your development environment for Civil 3D 2012. Check it out in case you are planning to upgrade.
Posted by: Isaac Rodriguez | 04/13/2011 at 06:31 PM