Aufrufen einer DLL aus einem Skriptwerkzeug

Die externe Funktionsbibliothek ctypes ist neu in Python 2.5. Sie stellt C-kompatible Datentypen bereit und ermöglicht das Aufrufen von Funktionen in DLLs oder freigegebenen Bibliotheken. Mit dem Modul ctypes in Python kann ArcObjects-Code, der in C++ verfasst wurde, in einem Geoverarbeitungsskriptwerkzeug verwendet werden.

Durch die Verwendung von ctypes in Python können Sie die Parameter und Typen leicht ändern, die vom Skriptwerkzeug erwartet werden, ohne den C++-Code neu kompilieren zu müssen. Das Modul ctypes unterstützt alle aufrufbaren C-Funktionen mit grundlegenden Datentypen, beispielsweise char, int, float und double sowie structs und Zeiger. Weitere Informationen zum Modul "ctypes" in Python 2.6.2 finden Sie unter 16.15 ctypes—A foreign function library for Python.

Das Aufrufen einer DLL aus einem Python-Skript bietet eine Reihe von Vorteilen. Sie können die komplexen ArcObjects-Klassen in Ihren Geoverarbeitungs-Tasks verwenden, die Rechte an Ihrem geistigen Eigentum bleiben gewahrt, und die Implementierung ist, verglichen mit einer Verwendung der Schnittstelle IGPFunction2 und der Schnittstelle IGPFunctionFactory, viel leichter. Erstellen Sie das Objekt in Python, und rufen Sie die execute-Methode auf. Übergeben Sie anschließend die erforderlichen Parameter vom Skript, und verwenden Sie dazu die Geoverarbeitungsumgebung.

Funktionsweise

Die Schritte lauten wie folgt:

Erstellen Sie in Visual Studio 2008 ein Win32-Projekt mit C++, das eine einfache Funktion mit dem Prototyp exportiert:

 int GpExecutetool(char* parameter1, char* parameter2)

Stellen Sie sicher, dass das Verzeichnis "ArcGIS\com\" im Projekt enthalten ist, und importieren Sie die ArcObjects-OLB-Dateien.

Erstellen Sie ein Skriptwerkzeug in einer benutzerdefinierten Toolbox, mit dem die beiden Parameter überprüft und an das Skript übergeben werden.

Das Python-Skript führt folgende Aktionen aus:

Details

Das C++-Projekt ("GPToolAsSimpleDLL" in diesem Beispiel) ist ein einfaches Win32-Projekt, in dem der Feature-Class ein AREA-Feld hinzugefügt und der Wert berechnet wird.

Die Header-Datei

#ifdef GPTOOLASSIMPLEDLL_EXPORTS
#define GPTOOLASSIMPLEDLL_API extern "C"__declspec(dllexport)
#else
#define GPTOOLASSIMPLEDLL_API extern "C"__declspec(dllimport)
#endif

GPTOOLASSIMPLEDLL_API int GPexecutetool(char*, char*);

Die Quelldatei "GPToolAsSimpleDLL" öffnet die angegebene Polygon-Feature-Class und legt das angegebene Feld auf die Fläche der einzelnen Polygon-Features fest. Alle Geoverarbeitungsfunktion, die Sie schreiben, können mit einem einfachen Einstiegspunkt für eine C-Funktion implementiert werden, alle in der gleichen DLL, sowie mit Zusätzen für Skriptwerkzeuge, die die einzelnen Funktionen für ArcToolbox verfügbar machen.

GPTOOLASSIMPLEDLL_API int GPexecutetool(char* featureclassPath, char* fieldname)
{
    // Convert char*s to bstring
    _bstr_t catalogPath;
    catalogPath = featureclasspath;
    _bstr_t newfieldname;
    newfieldname = fieldname;

    // Coinitialize GP utilities class
    IGPUtilitiesPtr ipUtil(CLSID_GPUtilities);

    // Feature class holder
    IFeatureClassPtr ipFeatureclass(0);

    HRESULT hr;

    // Try to fetch feature class from catalog path
    if (FAILED(hr = ipUtil->OpenFeatureClassFromString(catalogPath, &ipFeatureclass)))
    {
        return -1;
    }

    // Index position of the specified field
   	long fieldIndex;
   	ipFeatureclass->FindField(newfieldname, &fieldIndex);

    // Set up query filter and feature cursor
   	IQueryFilterPtr ipFilter(CLSID_QueryFilter);
   	IFeatureCursorPtr ipCursor;
   	IFeaturePtr ipRow;
   	IGeometryPtr ipShape;

    // Open search cursor on feature class
   	ipFeatureclass->Search(ipFilter, VARIANT_FALSE, &ipCursor);

    // Iterate
    esriGeometryType gt;
    for (ipCursor->NextFeature(&ipRow);
       		ipRow != NULL;
         ipCursor->NextFeature(&ipRow))
    {
         // Get row's associated geometry
		       ipRow->get_Shape(&ipShape);

		       // Ensure we've got a polygon
		       ipShape->get_GeometryType(&gt);
		       if (gt != esriGeometryPolygon)
			          return -2;

		       // Get area
		       IAreaPtr ipArea(ipShape);
         double area;
		       ipArea->get_Area(&area);

		       // Pop double into a variant
         VARIANT value;
		       value.vt = VT_R8;
		       value.dblVal = area;

		       // Set double variant onto target field
		       ipRow->put_Value(fieldIndex, value);

	       	// Save
	       	ipRow->Store();
    }

    return S_OK;
}

Das Python-Skript fungiert als Broker. Es akzeptiert die beiden Parameter vom Skriptwerkzeug als Text und sendet sie als nullterminierte char *-Zeichenfolgen an die DLL-Funktion. Bevor die Funktion in der DLL aufgerufen wird, verwendet das Skript auch das Geoverarbeitungswerkzeug "AddField". Dies kann den Workflow robuster machen, indem die proprietären Funktionen in C++ und die gängigen Tasks in Python implementiert werden.

import arcpy
import ctypes

# Get the parameters from the script tool dialog
#
shp = arcpy.GetParameterAsText(0)
fieldName = arcpy.GetParameterAsText(1)

# See if the field already exists in the feature class.
#   If not, add it.
if len(arcpy.ListFields(shp, fieldName)) == 0:
    arcpy.AddField_management(shp, fieldName, "DOUBLE")

# Import DLL into memory and get a pointer to the function
#   Note: be sure the DLL is in your Python search path 
#
dll = ctypes.cdll.GPToolAsSimpleDLL
perform_function = dll.GPExecutetool

# Tell ctypes the function accepts two char* arguments
#
perform_function.argtypes = [ctypes.c_char_p, ctypes.c_char_p]

# Tell ctypes the function return type
#
perform_function.restype = ctypes.c_int

# Call the function in the DLL
#
retval = perform_function(shp, fieldName)

# Check the return value.  If a 0 returned, success!
#
if retval == 0:
    arcpy.AddMessage("Success")
elif retval == 1:
    arcpy.AddError("Unable to open " + shp)
elif retval == 2:
    arcpy.AddError("Not a polygon feature class")


7/10/2012