通过脚本工具调用 DLL
Python 2.5 中新增了 ctypes,它是一种外部函数库。它提供兼容 C 的数据类型,且允许调用 DLL 或共享库中的函数。在 Python 中使用 ctypes 模块使您可在地理处理脚本工具中使用以 C++ 编写的 ArcObjects 代码。
在 Python 中使用 ctypes 使您可以轻松更改脚本工具所需的参数和类型,而不必重新编译 C++ 代码。ctypes 模块支持所有参数为基本数据类型的可调用 C 函数,这些基本数据类型包括 char、int、float、double 以及 structs 和指针等。有关 ctypes 模块在 Python 2.6.2 中的详细信息,请参阅 16.15 ctypes - Python 的一个外部函数库。
通过 Python 脚本调用 DLL 的优点很多。您可以在地理处理任务中使用细化的 ArcObjects 类,您的知识产权是受保护的,而且与必须使用 IGPFunction2 和 IGPFunctionFactory 接口相比执行起来更加简单快捷。只需使用 Python 创建对象并调用执行方法,然后即可通过地理处理框架从脚本传递所需的参数。
工作原理
具体步骤如下:
使用 Visual Studio 2008 创建一个 C++ Win32 项目,该项目通过原型导出一个简单函数:
int GpExecutetool(char* parameter1, char* parameter2)
务必在项目中包括 ArcGIS\com\directory 并导入 ArcObjects .olb 文件。
在自定义工具箱中创建一个脚本工具,用于验证两个参数并将它们传递到脚本中。
Python 脚本将执行以下操作:
- 导入 arcpy 和 ctypes。
- 从脚本工具中获取参数。
- 将 DLL 导入内存。
- 获取指向 DLL 中函数的指针。
- 通过设置 argtypes 属性指定从 DLL 中导出的函数所需的参数的类型,以及指定返回值的类型。
- 将参数传递到 DLL 中的 C++ 代码。
详细信息
C++ 项目(本例中名为 GPToolAsSimpleDLL 的项目)是简单的 Win32 项目,该项目可向要素类添加 AREA 字段并计算该字段的值。
头文件
#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*);
GPToolAsSimpleDLL 源文件用于打开指定的面要素类并为每个面要素的面积设置指定的字段。每个编写的地理处理函数均可使用简单的 C 函数入口点执行,并都位于同一个 DLL 中,它们与脚本工具的辅助工具一起将每个函数显示在 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(>);
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;
}
Python 脚本充当的是一个代理,它以文本形式接受脚本工具的两个参数并将它们作为 char*(以零结尾的字符串)传递给 DLL 函数。在调用 DLL 中函数之前,脚本还会使用 AddField 地理处理工具。通过使用 C++ 执行专有功能以及使用 Python 执行常见任务使工作流更加稳定。
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")