Cary Roys has posted a blog article titled Getting Started with InstallShield Automation and C# over at InstallShield. It's a good read that raises a couple thoughts for me. This blog post will address the first issue.
When you add a COM reference to InstallShield, Visual Studio will generate interop libraries specific to the version that you added. This allows you to call into the automation interface with code like:
ISWiAuto17.ISWiProject project = new ISWiAuto17.ISWiProject();
The problem with that though is the ISWiProject class is now coupled (strongly typed) to InstallShield 2011. If you need to reuse this code with other versions of InstallShield it simply isn't going to work.
There are several ways around this problem but none of them are pretty:
1) Use reflection for COM late binding.
//Untested Example
object projectType = Type.GetTypeFromProgID("ISWiAuto17.ISWiProject");
project = Activator.CreateInstance(projectType );
This gets ugly real fast with no type safety and a ton of line noise to invoke the members. If a version of InstallShield doesn't support a member, you'll get a run time error.
2) Switch to C# 4.0 and use the dynamic type. With this approach you create some initialization code that selects the correct version of InstallShield and assigns the project class to a dynamic class. This cuts down greatly on the line noise needed to write the code but it's still not type safe and what could be a build time error will be a run time error. Also you might be working in a build automation environment that hasn't yet migrated over to .NET 4.0.
3) Write your own interfaces and then implement provider pass through wrappers for all of the automation calls you'll need to support. This allows you to create a factory or use dependency injection. This is a very clean approach that will be type safe but takes a lot of time to write all the tedious plumbing.
4) It's been suggested to me that the the generated runtime callable wrapper could be modified so that the type names are constant. ( Version Neutral / Neutered interop ) but it's above my skill to know how to do this.
5) Hack it! Write your logic once, clone it to multiple class files and do a search and replace to update all the references. Then write a factory around it to let you control which one gets used. Any time you update your code you have to copy it over and search and replace again. This is a horrible design but it takes a lot less time to implement then option 3 but could possibly take more time to maintain over the long run. I've done this in the past and got lucky since the code base was pretty mature.
I'd love to see a better solution ( believe me, I've asked ), especially if it came built into InstallShield out of the box. Anyone have any suggestions?
1 comment:
We've done a variation of #5 recently, but what we did was put an #ifdef around the namespace of the classes. Then, we include all of the classes into multiple projects, each with a different namespace and different #ifdef value. This way you only need one copy of the classes, assuming the apis don't change signatures, etc.
Post a Comment