Sometimes we want to develop Revit addins with C++ instead of C# or VB.NET. Reasons may include but not limited to legacy C++ code reuse and code safety consideration. Performance should not be a big concern regarding Revit addin development as Revit does not provide any C++ libraries like MFC. It only provides a .NET API and even if C++ is used as the development language the IL (Intermediate Language) translation process still cannot be avoided and it results in pretty the same performance as using C# or VB.NET.
So if you are worried about performance, please do some detailed review work to your C# or VB.NET code and refine algorithms such as reducing loop count as much as possible, collecting what you need once for all rather than seeking every tiny little thing using pretty similar big loops again and again, making code more object oriented or structural, and so on. Moving to C++ cannot really resolve your performance concerns if the same algorithms are used, and doing so will actually bring in more complexities and make your development cycle longer.
After all using C#, VB.NET or C++ may be mostly just personal preferences and there is nothing wrong with either one. In this article let’s see how to set up C++ project configurations for Revit development especially the 64bit Revit in Visual Studio 2008.
At first hand, we need to create a Visual C++ CLR Class Library project and name it as RevitAddinVC8 for example:
We are targeting at Revit 2011 x64 so let the default .NET Framework 3.5 be and click the OK button.
Then we create a new Solution Platform for the x64 environment. As can be checked from the following Configuration Manager, only a Win32 Solution Platform is created by default. We drop down the Active solution platform combo box and click the <New…> item:
In the following New Solution Platform dialog, we create a new project platform through choosing the x64 platform and copying settings from the existing default Win32 platform:
Next, we double check some important properties of the configuration and platform combination of our concern, Debug|x64 here, and set them as expected if they are not.
Firstly, let’s look at the Framework and References property page:
As mentioned earlier, we are targeting Revit 2011 x64 so leave the Targeted Framework as .NET Framework 3.5. The .NET CLR Class Library wizard only adds a few very basic .NET assembly references for us such as the System and the System.Core. We may want to add some more .NET assemblies such as PresentationCore, System.Data, System.Data.DataSetExtensions, System.Data.Linq, System.Drawing, System.Windows.Forms, System.Xml, System.Xml.Linq, WindowBase and any others you like. More importantly, please do not forget to reference the two Revit API assemblies in, the RevitAPI and the RevitAPIUI.
Secondly, we need to make sure the Target Machine Linker setting is MachineX64 or /MACHINE:X64 instead of the default MachineX86. If the x64 platform is added by the Configuration Manager automatically as introduced above, the Target Machine will be automatically switched over from the /MACHINE:X86 to the expected /MACHINE:X64 but still it’s worth of a bit effort to double check the very important setting.
Thirdly, please make sure the /Zi Program Database is used as the Debug Information Format:
Fourthly, please double check the WIN64 preprocessor definition has successfully replaced the old default WIN32:
This guy is not taken care of well enough sometimes. Many times, it’s found that the WIN32 was still there after the x64 platform was added automatically by the Visual Studio Configuration Manager. If this happens and it’s not changed to the WIN64 manually, the Revit 2011 x64 will not warn anything but will just not load the addin assembly. The Visual Studio will not report anything either if the Revit is launched from inside its IDE. It makes things become a lot trickier.
Last but not least, if we’d like to debug the C++ CLR project, please copy the full path of the Revit.exe to the Debugging Command setting:
Now it’s time to look after coding stuffs since the project configurations, platforms, and settings are ready.
Add one more header file, Namespaces.h, and copy and paste the following code in:
#pragma once
using namespace Autodesk::Revit::ApplicationServices;
using namespace Autodesk::Revit::Attributes;
using namespace Autodesk::Revit::DB;
using namespace Autodesk::Revit::DB::Events;
using namespace Autodesk::Revit::DB::Architecture;
using namespace Autodesk::Revit::DB::Structure;
using namespace Autodesk::Revit::DB::Mechanical;
using namespace Autodesk::Revit::DB::Electrical;
using namespace Autodesk::Revit::DB::Plumbing;
using namespace Autodesk::Revit::UI;
using namespace Autodesk::Revit::UI::Selection;
using namespace Autodesk::Revit::UI::Events;
using namespace Autodesk::Revit::Collections;
using namespace Autodesk::Revit::Exceptions;
using namespace Autodesk::Revit::Utility;
namespace RvtAppSrv = Autodesk::Revit::ApplicationServices;
namespace RvtDB = Autodesk::Revit::DB;
Add the following content to the header, Stdafx.h, which was created by the C++ CLR Class Library:
#pragma once
#using <mscorlib.dll>
using namespace System;
using namespace System::Collections;
using namespace System::Collections::Generic;
using namespace System::Text;
using namespace System::Linq;
using namespace System::Xml;
using namespace System::Reflection;
using namespace System::ComponentModel;
using namespace System::Globalization;
using namespace System::Resources;
using namespace System::Threading;
using namespace System::IO;
using namespace System::Windows;
using namespace System::Windows::Media::Imaging;
using namespace System::Windows::Forms;
using namespace System::Windows::Interop;
using namespace System::Drawing;
using namespace System::Drawing::Imaging;
Add one more source, ExtApp.cpp, and copy/paste the following code in it:
#include "StdAfx.h"
#include "Namespaces.h"
namespace RevitAddinVC8
{
[Transaction(TransactionMode::Manual)]
[Regeneration(RegenerationOption::Manual)]
public ref class ExtApp : IExternalApplication
{
#pragma region Cached Variables
public:
static UIControlledApplication^ _cachedUiCtrApp;
#pragma endregion
#pragma region IExternalApplication Members
virtual Result OnStartup(UIControlledApplication^ uiApp)
{
_cachedUiCtrApp = uiApp;
try
{
RibbonPanel^ ribbonPanel = CreateRibbonPanel();
//TODO: add you code below->
return Result::Succeeded;
}
catch (Exception^ ex)
{
MessageBox::Show( ex->ToString() );
return Result::Failed;
}
}
virtual Result OnShutdown(UIControlledApplication^ uiApp)
{
try
{
//TODO: add you code below->
return Result::Succeeded;
}
catch (Exception^ ex)
{
MessageBox::Show(ex->ToString());
return Result::Failed;
}
}
#pragma endregion
#pragma region Local Methods
private:
RibbonPanel^ CreateRibbonPanel()
{
RibbonPanel^ panel = _cachedUiCtrApp->CreateRibbonPanel("RevitAddinVC8");
////Default button:
PushButtonData^ pbDataExtCmd = gcnew PushButtonData("ExtCmd", "ExtCmd", Assembly::GetExecutingAssembly()->Location, "RevitAddinVC8.ExtCmd");
PushButton^ pbExtCmd = static_cast<PushButton^>(panel->AddItem(pbDataExtCmd));
pbExtCmd->ToolTip = "ExtCmd";
pbExtCmd->LargeImage = BmpImageSource("ExtCmd32x32");
pbExtCmd->Image = BmpImageSource("ExtCmd16x16");
////More buttons:
return panel;
}
System::Windows::Media::ImageSource^ BmpImageSource(String^ embeddedPath)
{
ResourceManager^ resources = gcnew ResourceManager("RevitAddinVC8.Resources", Assembly::GetExecutingAssembly());
System::Drawing::Bitmap^ img = (cli::safe_cast<System::Drawing::Bitmap^>(resources->GetObject(embeddedPath)));
IntPtr hBitmap = img->GetHbitmap();
return System::Windows::Interop::Imaging::CreateBitmapSourceFromHBitmap(hBitmap, IntPtr::Zero, Int32Rect::Empty, BitmapSizeOptions::FromEmptyOptions());
}
#pragma endregion
};
}
Add another source, ExtCmd.cpp, and copy/paste the following code in:
#include "stdafx.h"
#include "Namespaces.h"
namespace RevitAddinVC8
{
[Transaction(TransactionMode::Manual)]
[Regeneration(RegenerationOption::Manual)]
public ref class ExtCmd : IExternalCommand
{
#pragma region Cached Variables
private:
static ExternalCommandData^ _cachedCmdData;
public:
static property UIApplication^ CachedUiApp
{
UIApplication^ get()
{
return _cachedCmdData->Application;
}
}
static property RvtAppSrv::Application^ CachedApp
{
RvtAppSrv::Application^ get()
{
return CachedUiApp->Application;
}
}
static property RvtDB::Document^ CachedDoc
{
RvtDB::Document^ get()
{
return CachedUiApp->ActiveUIDocument->Document;
}
}
#pragma endregion
#pragma region IExternalCommand Members
public:
virtual Result Execute(ExternalCommandData^ cmdData, String^% msg, ElementSet^ elemSet)
{
_cachedCmdData = cmdData;
try
{
//TODO: add your code below.
return Result::Succeeded;
}
catch(System::Exception^ ex)
{
msg = ex->ToString();
return Result::Failed;
}
return Result::Succeeded;
}
#pragma endregion
};
}
Create a Revit addin manifest file and name it as RevitAddinVC8.Addin for example and copy/paste the following external application registration nodes in:
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
<AddIn Type="Application">
<Assembly>C:\Temp\RevitAddinVC8\Debug\RevitAddinVC8.dll</Assembly>
<FullClassName>RevitAddinVC8.ExtApp</FullClassName>
<ClientId>aeb8f380-2f91-4fd4-8f55-80a7b574f233</ClientId>
<Name>RevitAddinVC8</Name>
</AddIn>
</RevitAddIns>
Put the manifest file into the user roaming folder of the Revit 2011 Addins (e.g. C:\Users\<YOU>\AppData\Roaming\Autodesk\Revit\Addins\2011\RevitAddinVC8 - Test.Addin on Windows 7), and then you are all set.
Ok, now set a breakpoint in the C++ project, and press the F5 button. The Revit 2011 x64 will be launched, the C++ Revit addin assembly will be registered and loaded automatically, and the ribbon panel and button will be created and added to the Revit ribbon Addins tab automatically, when the button is clicked, the external command will be called and if you set a breakpoint somewhere there it will be reached.
By the way, curious people may wonder what will happen if a WIN32 C++/CLI addin is trying to be debugged on a 64bit Revit? Here it is:
The Visual Studio Debugger complains that “The debugger does not support debugging managed and native code at the same time on this platform.”! It does not mention anything about x64 or x86!
Obviously the message is rather vague and confusing but we get the idea!
RevitAddinWizard will take care of all these automatically and make everything ready for you in a moment for both 32bit and 64bit Revit and both 2008 and 2010 Visual Studio.
Recent Comments