Extending ArcPy for Increased Capabilities with ArcObjects and C#

Books on shelves in library or study

I love Python, and it naturally follows that I love ArcPy also. It’s far easier to do simple data management tasks in a scripting language like ArcPy rather than trying to do anything in ArcObjects. There is no license initialization, the commands are concise and the overall API is lightweight…

But….

Sometimes I’m halfway through writing something in a Python script, and the ArcPy function I need just isn’t there! It’s a pretty powerful API but it’s not comprehensive. If the function I needed was only available in the ArcObjects API, my option was typically to write an EXE that did the ArcObjects part and then I would plug that into the script. This has some major downsides, including performance and potentially schema or data locking. Because the EXE would be running out of process from the rest of the Python script, license initialization must be done again (slow!), and the workspace pool will be different, so a new workspace will need to be opened (slow and potential locks!).

Recently I was trying to build a custom post function, that I’d previously written in ArcObjects with C#, into an existing Python script. Rewriting the whole thing in Python wasn’t possible due to the need for fine grained interfaces for difference cursors, and writing everything as in ArcObjects/C# wasn’t particularly desirable as the script was already heavily using Python libraries for other parts of the script.

Enter Robert Giesecke’s wonder DLL

So I came across this brilliant library on NuGet called UnmanagedExports from Robert Giesecke. This in turn allows me to create functions that I can call directly from Python, using the ctypes library. To install the library use the NuGet package manager:

Install UnmanagedExports to Library
Figure 1: Install UnmanagedExports to library

This allows you to write functions in C#, and then export them as unmanaged dll functions, like how you would do in C++ with dllexport. Once you reference the library in your C# project, it allows you to define which functions you wish to export, and at compile time, the library will modify the IL to add the required exports to your compiled DLL. 

What is extremely cool about this library is that it does everything at compile time, therefore, no extra DLL needs to be deployed! So, you’ll never see the RGiesecke.DllExport.Metadata.dll in your deployment folder.

Example Function

Here is an example of how you would write a function that you want to expose. The name defined in the first parameter of DllExport is the name that will be called from Python.

Python’s Ctypes Library

I know what you’re thinking at this point. There’s already a COM library in Python that can directly call ArcObjects, why not just use that and not even worry about the unmanaged exports? Simple: that library doesn’t come with the ArcGIS installation of Python, so it’s easier to just deploy the script and the dll, as we shall show here, and the ctypes library IS available and very easy to use.

Example Call Function

Here’s an example of a Python script that would call the function we wrote above.

What’s cool about this is that no license initialization was required inside the exported function or DLL. The script initializing ArcPy is enough for ArcObjects to be initialized under the hood and we’re good to go. This means we can call quick ArcObjects functions repeatedly and get good performance.

Where is This Useful?

The example situation I went through in this blog is just one of many different reasons you may want to use this export capability in C#. Other good reasons include: 

• If you’re a product developer wanting to create a Python API to your .NET library (with or without ArcObjects)

• If you have a desire to migrate your legacy ArcObjects code to ArcPy over a period of time, this could be a good way to start

A Few More Details…

1. For your .NET DLL to work in the 32 bit Python that comes with ArcGIS, make sure the x86 build type is selected in your project properties.

Select 86 build type in project properties
Figure 2: 86 build type selected in project properties

2. If you’re referencing other DLLs you’ve written from your exported DLL, they must be signed and in the GAC. The ctypes library seems to have problems linking to other DLLs, even if they are in the same folder as either the script or the exported DLL.

3. The example above shows returning just an integer number. To work with other simple types, I found the following:

a. When returning a bool, it just returns as a 1 / 0 for True/False respectively

b. When returning a string, you must use the following function in Python to get the value as it’s returned as a pointer

• stringvalue = ctypes.cast(result, ctypes.c_char_p).value

Contact us to learn how to further extend ArcPy for your organization.

Malcolm Lobley headshot

12 years at UDC / 23 years in GIS

Malcolm Lobley

Malcolm is an architect and software development consultant for UDC with a focus on Esri technologies. He provides valuable technical architecture expertise for UDC client projects through leading Geodatabase and GIS solution architecture activities. His work includes leading the Gas Distribution GIS projects for Pacific Gas and Electric.