Appel d'une DLL à partir d'un outil de script

ctypes, une bibliothèque de fonctions étrangère, est une nouveauté de la version 2.5 de Python. Elle fournit des types de données compatibles avec le langage C et permet d'appeler des fonctions qui résident dans des fichiers DLL ou des bibliothèques partagées. Le module ctypes de Python permet d'utiliser du code ArcObjects écrit dans C++ dans un outil de script de géotraitement.

Le module ctypes de Python vous permet de modifier facilement les paramètres et les types que l'outil de script attend sans devoir recompiler notre code C++. Le module ctypes prend en charge toutes les fonctions pouvant être appelées avec le langage C avec des types de données de base, tels que char, int, float et double ainsi que des structures et des pointeurs. Pour plus d'informations sur le module ctypes de Python 2.6.2, reportez-vous à la rubrique ctypes 16.15 ctypes - Une bibliothèque de fonctions étrangère pour Python.

La possibilité d'appeler une DLL à partir d'un script Python présente de nombreux avantages. Vous pouvez tirer parti des classes d'objet ArcObjects détaillées dans vos tâches de géotraitement : votre propriété intellectuelle est protégée. En outre, il beaucoup plus facile de les implémenter que d'utiliser des interfaces IGPFunction2 et IGPFunctionFactory. Vous créez votre objet dans Python et appelez la méthode Execute, pour transférer les paramètres requis à partir du script dans la structure de géotraitement.

Fonctionnement

La procédure est la suivante :

Créez un projet de C++ Win32 dans Visual Studio 2008 chargé d'exporter une fonction simple avec le prototype : 

 int GpExecutetool(char* parameter1, char* parameter2)

Veillez à inclure le répertoire ArcGIS\com\ dans le projet et importez les fichiers ArcObjects .olb.

Créez un outil de script dans une boîte à outils personnalisée qui valide les deux paramètres et les transmet au script.

Votre script Python effectuera les opérations suivantes :

Détails

Le projet C++ (nommé GPToolAsSimpleDLL dans cet exemple) est un projet Win32 simple qui ajoute un champ AREA à la classe d'entités et calcule la valeur.

Le fichier d'en-tête

#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*);

Le fichier source GPToolAsSimpleDLL ouvre la classe d'entités surfaciques spécifiée et définit le champ spécifié sur la surface de chaque entité surfacique. Chaque fonction de géotraitement que vous programmez peut être implémentée avec un point d'entrée de fonction C simple, dans la même DLL, avec des outils de script complémentaires chargés d'exposer chaque fonction auprès d'ArcToolBox.

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;
}

Le script Python joue le rôle d'intermédiaire : il accepte en entrée les deux paramètres de l'outil de script sous forme de texte et les transmet à la fonction DLL en tant que chaînes char* qui se terminent par un zéro. Le script utilise également l'outil de géotraitement AddField avant que la fonction ne soit appelée dans la DLL. Le fait d'implémenter votre fonctionnalité dans C++ et les tâches courantes dans Python présente l'avantage de pouvoir rendre votre workflow plus robuste.

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