ArcObjects is based on the Component Object Model (COM), and for .NET objects to interoperate with COM objects, an intermediate layer is needed. The .NET Framework contains the Interop API which acts as the intermediate layer between .NET and COM. The Interop API provides Runtime Callable Wrappers (RCW) provide the mechanism to use COM objects in .NET. For example, when defining a .COM object in .NET like this:
ESRI.ArcGIS.Geometry.Point pt = new ESRI.ArcGIS.Geometry.Point();
DimptAsESRI.ArcGIS.Geometry.Point =NewESRI.ArcGIS.Geometry.Point()
A Runtime Callable Wrapper object is defined instead of the actual COM object itself. When the object is instantiated (using the new keyword) the wrapper object creates the appropriate COM object. When you call the members of the object in .NET, the parameters of the method call will be marshaled from the .NET process to the COM process and the return value will be marshaled back.
Therefore, to analyze the performance of ArcObjects in .NET, you need to consider the overhead of creating wrapper objects and marshaling, as compared to the actual running time of your ArcObjects code. The code below gives an example of this comparison.
NOTE: This example code is not intended as a benchmark of ArcObjects performance in .NET, but demonstrates possible differences in the speed of execution of ArcObjects code which you may wish to consider when creating applications. The example code was run on a machine with a processor speed of 2GHz, with NET Framework version 1.1, using Visual Studio .NET 2003.
First, create a Windows application in C# or VB.NET, add a form with one button, and perform some simple geometry operations in a loop when the button is clicked.
private voidbutton1_Click(objectsender, System.EventArgs e) {// Prepare the objects.ESRI.ArcGIS.Geometry.IPoint p1, p2; p1 =newESRI.ArcGIS.Geometry.Point(); p2 =newESRI.ArcGIS.Geometry.Point(); p1.PutCoords(1, 2); p2.PutCoords(3, 4); ESRI.ArcGIS.Geometry.ILine pLine =newESRI.ArcGIS.Geometry.Line();// Time a loop of geometry operations.inttick1, tick2, tick3; tick1 = System.Environment.TickCount;for(longi = 0; i <= 10000000; i++) { pLine.PutCoords(p1, p2); } tick2 = System.Environment.TickCount; tick3 = tick2 - tick1; System.Windows.Forms.MessageBox.Show(tick3.ToString()); }
Private SubButton1_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesButton1.Click' Prepare the objects.Dimp1, p2AsESRI.ArcGIS.Geometry.IPoint p1 =NewESRI.ArcGIS.Geometry.Point p2 =NewESRI.ArcGIS.Geometry.Point p1.PutCoords(1, 2) p2.PutCoords(3, 4)DimpLineAsESRI.ArcGIS.Geometry.ILine =NewESRI.ArcGIS.Geometry.Line' Time a loop of geometry operations.Dimtick1, tick2, tick3As Integertick1 = System.Environment.TickCountDimiAs IntegerFori = 0To10000000 pLine.PutCoords(p1, p2)Nexti tick2 = System.Environment.TickCount tick3 = tick2 - tick1 System.Windows.Forms.MessageBox.Show(tick3.ToString())End Sub
This code took an average of around 30 seconds to run in C# or VB.NET. For comparison, use Visual Basic 6 to create a Standard EXE, using code that like that below. (The GetTickCount Windows API call replaces the .NET Framework TickCount property).
Option ExplicitPrivate Declare FunctionGetTickCountLib"kernel32.dll" ()As LongPrivate SubCommand1_Click()' Prepare the objects.Dimp1AsesriGeometry.IPointSetp1 =NewesriGeometry.PointDimp2AsesriGeometry.IPointSetp2 =NewesriGeometry.Point p1.PutCoords 1, 2 p2.PutCoords 3, 4DimpLineAsesriGeometry.ILineSetpLine =NewesriGeometry.Line' Get tickcount and start the iterations.Dimtick1As Long, tick2As Long, tick3As Longtick1 = GetTickCount()DimiAs LongFori = 0To9999999 pLine.PutCoords p1, p2Nexti tick2 = GetTickCount() tick3 = tick2 - tick1 MsgBox CStr(tick3)End Sub
This equivalent VB 6 code takes around 8 seconds to execute, making the code around three and a half times faster than .NET! However, the example presented above is an extreme case; doing small operations which execute very quickly, but performing the execution thousands of times. In cases like this, the time spent in the interop between COM and .NET is likely to dominate the total running time.
Consider instead example code which creates and populates a personal geodatabase, based on the Greeley sample data. A personal geodatabase is first created, then populated with features by importing the shapefiles which comprise the Greeley sample data. Subtypes are created, and a geometric network with connectivity rules is built. Also a composite relationship class is created. In contrast to example one, this code consists of operations which take a longer time to execute.
In C# and VB.NET, this code takes an average of 25 seconds to run, and in VB6 the execution time also averages around 25 seconds. How can the .NET code execute just as fast, considering the extra instructions required for the interop layer? The VB6 code is executed by the Visual Basic runtime, which can be less efficient than the .NET framework. For this example then, no difference is seen in performance between the environments.
Realistically of course, you may well need to perform exactly the kind of operation which took so much longer in .NET - large iterations of small operations. Do you have to take the performance hit?
Not necessarily. You have the choice to write a proxy class in Managed C++, where you can place ArcObjects code which uses the interop layer heavily. Once you expose this code as public methods, you can call the methods from your C# or VB.NET code. As the interop layer is only required when you call the methods of this proxy class, the execution time can be greatly reduced.
For example, to perform the previous operations, you can create a proxy class in Managed C++. This class will contain a function which performs the loop of geometry operations.
#using"C:\Program Files\ArcGIS\Dotnet\ESRI.ArcGIS.System.dll"#using"C:\Program Files\ArcGIS\Dotnet\ESRI.ArcGIS.Geometry.dll"namespaceMCpp {public __gc classAOWrapper {public: AOWrapper() { ::CoInitialize(0); } ~AOWrapper() { ::CoUninitialize(); }public:intline_test() { IPointPtr ipPt1(CLSID_Point); IPointPtripPt2(CLSID_Point); ipPt1->PutCoords(1, 2); ipPt2->PutCoords(2, 3); ILinePtr ipLine(CLSID_Line);for(longi = 0; i < = 10000000; i++) { ipLine->PutCoords(ipPt1,ipPt2); }return0; } }; }
This function can be called from a .NET application using code like that below (remember to add a reference to the Dll created previously).
private voidbutton1_Click(objectsender, System.EventArgs e) {inttick1, tick2, tick3; MCpp.AOWrapper w =newMCpp.AOWrapper(); tick1 = System.Environment.TickCount; w.line_test(); tick2 = System.Environment.TickCount; tick3 = tick2 - tick1; System.Windows.Forms.MessageBox.Show(tick3.ToString()); }
Private SubButton1_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesButton1.ClickDimtick1, tick2, tick3As IntegerDimwAsMCpp.AOWrapper =NewMCpp.AOWrapper() tick1 = System.Environment.TickCountFori = 0To10000000 pLine.PutCoords(p1, p2)Nexti tick2 = System.Environment.TickCount tick3 = tick2 - tick1 System.Windows.Forms.MessageBox.Show(tick3.ToString())End Sub
Using the proxy class written in Managed C++, the average running time is improved to around 9 seconds.
One disadvantage of using Managed C++ is that the assembly written in Managed C++ cannot use the verification mechanism of .NET security, so it needs to run in a trusted environment. Future releases of Visual C++ .NET may add additional functionality which may affect this situation.
The key to improving performance is to find the performance bottleneck of your application, which may or may not be the interop time. For most of ArcObjects applications, we do not expect developing in .NET will significantly deteriorate the performance of your code. If you find the interop indeed is the bottleneck, writing Managed C++ proxy classes for parts of your code could provide a solution.
Microsoft Corporation, " Managed Extensions for C++ Programming," msdn.microsoft.com/library/?url=/library/en-us/vcmex/html/vcconmcoverview.asp?frame=true, 2002
Microsoft Corporation, "Dr. GUI on Visual C++ .NET," msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/drgui02132002.asp, 2002