Creating a plug-in data source


Summary
A plug-in data source integrates a new data format completely into ArcGIS, albeit in a read-only manner. This topic is a guide for the steps to implement a plug-in data source for ArcGIS.


Creating a plug-in data source

To create a plug-in data source, implement the following required classes:
  • Plug-in workspace factory helper
  • Plug-in workspace helper
  • Plug-in dataset helper
  • Plug-in cursor helper
Sometimes these classes are referred to generically with the prefix PlugIn, for example, PlugInWorkspaceHelper. In addition to these required classes, a plug-in data source can have an optional plug-in extension class and possibly several plug-in native type classes. With each class, there are one or more interfaces that must be implemented.

SimplePoint plug-in data source

Imagine that there is a regular supply of text files containing geographic locations, but the data in the files has an unusual format. This data should be used in ArcGIS, but converting data each time a new file is received is impractical. In short, ArcGIS should work directly with this data, as it does with other supported data formats. This can be done by implementing a plug-in data source.
The SimplePoint plug-in data source provides direct ArcGIS support for unusual data formats.
The following screen shot uses a simple format. An American Standard Code for Information Interchange (ASCII) text file contains data for each new point on a new line. The first six characters are the x-coordinate, the next six characters contain the y-coordinate, and the trailing characters contain an attribute value. For more information, see Simple point plug-in data source.

Implementing a plug-in workspace factory helper

A workspace factory helper class must implement IPlugInWorkspaceFactoryHelper. This helper class works in conjunction with the existing ArcGIS PlugInWorkspaceFactory class.
The PlugInWorkspaceFactory class implements IWorkspaceFactory and uses the plug-in workspace factory helper to get information about the data source, and to browse for workspaces. Together, they act as a workspace factory for the data source.
The most important part of the implementation is the return value of IPlugInWorkspaceFactoryHelper.WorkspaceFactoryTypeID. It should return a class identifier (CLSID) that does not refer to any implementation. It is used as an alias for the workspace factory of the data source that is created by PlugInWorkspaceFactory. See the following code example:
[C#]
public UID WorkspaceFactoryTypeID
{
  get
  {
     UID wkspFTypeID = new UIDClass();
     wkspFTypeID.Value = "{b8a25f89-2adc-43c0-ac2e-16b3a88e3915}"; //Proxy.
     return wkspFTypeID;
  }
}
[VB.NET]
Public ReadOnly Property WorkspaceFactoryTypeID() As UID Implements IPlugInWorkspaceFactoryHelper.WorkspaceFactoryTypeID
   Get
      Dim wkspFTypeID As UID = New UIDClass()
      wkspFTypeID.Value = "{b8a25f89-2adc-43c0-ac2e-16b3a88e3915}" 'Proxy.
      Return wkspFTypeID
   End Get
End Property
A plug-in workspace factory helper should be registered in the component category ESRI PlugIn Workspace Factory Helpers, and once compiled, should have the ESRIRegAsm utility called on the resulting assembly. 
The remaining implementation of IPlugInWorkspaceFactoryHelper is relatively straightforward. The most difficult member to implement is often GetWorkspaceString. The workspace string is used as a lightweight representation of the workspace.
The plug-in is the sole consumer (IsWorkspace and OpenWorkspace) of the strings; therefore, their content is the developer's decision. For many data sources, including this example, the path to the workspace is chosen as the workspace string. One thing to note about GetWorkspaceString is the FileNames parameter. This parameter can be null, in which case, IsWorkspace should be called to determine if the directory is a workspace of the plug-in's type. If the parameter is not null, examine the files in FileNames to determine if the workspace is of the plug-in's type. Also, remove any files from the array that belong to the plug-in data source. This behavior is comparable to that of IWorkspaceFactory.GetWorkspaceName.
The DataSourceName property is simple to implement (just return a string representing the data source). The example returns SimplePoint. This is the only text string that should not be localized. The other strings (for example, by using a resource file) should be localized if the plug-in data source could be used in different countries. For simplicity, the example does not localize its strings.
The OpenWorkspace method creates an instance of the next class that must be implemented, the plug-in workspace helper. A way of initializing the workspace helper with the location of the data is needed. See the following code example:
[C#]
public IPlugInWorkspaceHelper OpenWorkspace(string wksString)
{
    if (System.IO.Directory.Exists(wksString))
    {
        SimplePointWksp openWksp = new SimplePointWksp(wksString);
        return (IPlugInWorkspaceHelper)openWksp;
    }
    return null;
}
[VB.NET]
Public Function OpenWorkspace(ByVal wksString As String) As IPlugInWorkspaceHelper Implements IPlugInWorkspaceFactoryHelper.OpenWorkspace
    If System.IO.Directory.Exists(wksString) Then
       Dim openWksp As SimplePointWksp = New SimplePointWksp(wksString)
       Return CType(openWksp, IPlugInWorkspaceHelper)
    End If
   Return Nothing
End Function
Plug-in workspace factories can also implement the optional interface, IPlugInCreateWorkspace, to support creation of workspaces for a plug-in data source. For more information, see Implementing optional functionality on plug-in data sources.

Implementing a plug-in workspace helper

A plug-in workspace helper represents a single workspace for datasets of the data source type. The class does not need to be publicly creatable, as the plug-in workspace factory helper is responsible for creating it in its OpenWorkspace method. See the following illustration:
The class must implement IPlugInWorkspaceHelper. This interface allows the data source's datasets to be browsed. The most noteworthy member is OpenDataset, which creates and initializes an instance of a plug-in dataset helper. See the following code example:
[C#]
public IPlugInDatasetHelper OpenDataset(string localName)
{
    if (m_sWkspPath == null)
       return null;
    SimplePointDataset ds = new SimplePointDataset(m_sWkspPath, localName);
       return (IPlugInDatasetHelper)ds;
}
[VB.NET]
Public Function OpenDataset(ByVal localName As String) As IPlugInDatasetHelper Implements IPlugInWorkspaceHelper.OpenDataset
    If m_sWkspPath Is Nothing Then
       Return Nothing
    End If
    Dim ds As SimplePointDataset = New SimplePointDataset(m_sWkspPath, localName)
       Return CType(ds, IPlugInDatasetHelper)
End Function
If the CanSupportSQL property of IPlugInWorkspaceFactoryHelper returns true, the plug-in workspace helper should implement the ISQLSyntax interface. In this case, the workspace object delegates calls to its ISQLSyntax to the interface on this class. The ArcGIS framework passes WHERE clauses to the IPlugInDatasetHelper.FetchAll and FetchByEnvelope methods, and the cursors returned by these functions should contain only rows that match the WHERE clause.
If CanSupportSQL returns false, the ArcGIS framework does not pass WHERE clauses but handles the postquery filtering. The advantage of implementing support for WHERE clauses is that queries can be processed on large datasets more efficiently than with a postquery filter. The disadvantage is the extra implementation code required. The example returns false for CanSupportSQL and leaves handling of WHERE clauses to the ArcGIS framework.
A plug-in workspace helper can implement IPlugInMetadata or IPlugInMetadataPath to support metadata. Implement IPlugInMetadata if the data source has its own metadata engine. This interface allows metadata to be set and retrieved as property sets. Otherwise, implement IPlugInMetadataPath, which allows the plug-in to specify a metadata file for each dataset. ArcGIS uses these files for storing metadata. Implement one of these interfaces for successful operation of the Export Data command in ArcMap. This command uses the FeatureDataConverter object, which relies on metadata capabilities of data sources.
A plug-in workspace helper can also implement the optional interfaces IPlugInWorkspaceHelper2 and IPlugInLicense.

Implementing a plug-in dataset helper

A plug-in dataset helper class must implement the IPlugInDatasetInfo and IPlugInDatasetHelper interfaces. It does not need to be publicly creatable, as a plug-in workspace helper is responsible for creating it. See the following illustration:
IPlugInDatasetInfo provides information about the dataset so that the user interface (UI) can represent it. For example, ArcCatalog uses this interface to display an icon for the dataset. To enable fast browsing, it is important that the class have a low creation overhead. In the Simple point plug-in data source sample, the SimplePointDataset class can be created and all the information for IPlugInDatasetInfo derived without opening the data file.
IPlugInDatasetHelper provides more information about the dataset and methods to access the data. If the dataset is a feature dataset (that is, it contains feature classes), all the feature classes are accessed via a single instance of this class. Many of the interface members have a ClassIndex parameter that determines which feature class is being referenced.
The columns of the dataset are defined by IPlugInDatasetHelper.Fields. For the SimplePoint data source, all datasets have the following four fields—ObjectID, Shape, and two attribute fields that, in the example, are arbitrarily named ColumnOne and Extra. When implementing fields, the spatial reference of the dataset must be defined. In the example, for simplicity, an UnknownCoordinateSystem is used. If the spatial reference is a geographic coordinate system, put the extent of the dataset into the IGeographicCoordinateSystem2.ExtentHint property before setting the domain of the spatial reference. Setting the domain first can cause problems with projections and export. See the following code example:
[C#]
public IFields get_Fields(int ClassIndex)
{
 IFieldEdit fieldEdit;
 IFields fields;
 IFieldsEdit fieldsEdit;
 IObjectClassDescription fcDesc;
 if (this.DatasetType == esriDatasetType.esriDTTable)
    fcDesc = new ObjectClassDescriptionClass();
 else
    fcDesc = new FeatureClassDescriptionClass();

 fields = fcDesc.RequiredFields;
 fieldsEdit = (IFieldsEdit)fields;

   fieldEdit = new FieldClass();
 fieldEdit.Length_2 = 1;
 fieldEdit.Name_2 = "ColumnOne";
 fieldEdit.Type_2 = esriFieldType.esriFieldTypeString;
 fieldsEdit.AddField((IField)fieldEdit);

 //HIGHLIGHT: Add extra int column.
 fieldEdit = new FieldClass();
 fieldEdit.Name_2 = "Extra";
 fieldEdit.Type_2 = esriFieldType.esriFieldTypeInteger;
 fieldsEdit.AddField((IField)fieldEdit);

 //HIGHLIGHT: Set shape field geometry definition.
 if (this.DatasetType != esriDatasetType.esriDTTable)
 {
    IField field = fields.get_Field(fields.FindField("Shape"));
    fieldEdit = (IFieldEdit)field;
    IGeometryDefEdit geomDefEdit = (IGeometryDefEdit)field.GeometryDef;
    geomDefEdit.GeometryType_2 = geometryTypeByID(ClassIndex);
    ISpatialReference shapeSRef = this.spatialReference;

    #region M & Z
   //M.
   if ((ClassIndex >= 3 && ClassIndex <=5) || ClassIndex >= 9)
   {
      geomDefEdit.HasM_2 = true;
      shapeSRef.SetMDomain(0, 1000);
   }
   else
      geomDefEdit.HasM_2 = false;
   
    //Z.
   if (ClassIndex >= 6)
   {
      geomDefEdit.HasZ_2 = true;
      shapeSRef.SetZDomain(0, 1000);
   }
   else
      geomDefEdit.HasZ_2 = false;
   #endregion

   geomDefEdit.SpatialReference_2 = shapeSRef;
 }

 return fields;
}
[VB.NET]
Public ReadOnly Property Fields(ByVal ClassIndex As Integer) As ESRI.ArcGIS.Geodatabase.IFields Implements ESRI.ArcGIS.Geodatabase.IPlugInDatasetHelper.Fields
Get
  Dim fieldEdit As IFieldEdit
  Dim flds As IFields
  Dim fieldsEdit As IFieldsEdit
  Dim fcDesc As IObjectClassDescription
  If Me.DatasetType = esriDatasetType.esriDTTable Then
     fcDesc = New ObjectClassDescriptionClass()
  Else
     fcDesc = New FeatureClassDescriptionClass()
  End If

  flds = fcDesc.RequiredFields
  fieldsEdit = CType(flds, IFieldsEdit)

  fieldEdit = New FieldClass()
  fieldEdit.Length_2 = 1
  fieldEdit.Name_2 = "ColumnOne"
  fieldEdit.Type_2 = esriFieldType.esriFieldTypeString
  fieldsEdit.AddField(CType(fieldEdit, IField))

  'HIGHLIGHT: Add extra int column.
  fieldEdit = New FieldClass()
  fieldEdit.Name_2 = "Extra"
  fieldEdit.Type_2 = esriFieldType.esriFieldTypeInteger
  fieldsEdit.AddField(CType(fieldEdit, IField))

  'HIGHLIGHT: Set shape field geometry definition.
  If Me.DatasetType <> esriDatasetType.esriDTTable Then
     Dim field As IField = flds.Field(flds.FindField("Shape"))
     fieldEdit = CType(field, IFieldEdit)
     Dim geomDefEdit As IGeometryDefEdit = CType(field.GeometryDef, IGeometryDefEdit)
     geomDefEdit.GeometryType_2 = geometryTypeByID(ClassIndex)
     Dim shapeSRef As ISpatialReference = Me.spatialReference

     ' #Region "M & Z."
     'M.
     If (ClassIndex >= 3 AndAlso ClassIndex <= 5) OrElse ClassIndex >= 9 Then
        geomDefEdit.HasM_2 = True
        shapeSRef.SetMDomain(0, 1000)
     Else
        geomDefEdit.HasM_2 = False
     End If
    
      'Z.
     If ClassIndex >= 6 Then
        geomDefEdit.HasZ_2 = True
        shapeSRef.SetZDomain(0, 1000)
     Else
        geomDefEdit.HasZ_2 = False
     End If
     ' #End Region.

    geomDefEdit.SpatialReference_2 = shapeSRef
  End If
Return flds
End Get
End Property
All data sources must include an ObjectID field. If the plug-in's data does not have a suitable unique integer field, values must be generated on-the-fly. The example uses the current line number in the text file as the ObjectID. Another data source without explicit ObjectIDs is the shapefile format. In a similar way, the ArcGIS framework generates a suitable unique integer automatically for each feature in a shapefile.
The following are three similar members of IPlugInDatasetHelper that all open a cursor on the dataset:
In the example, all these methods create a plug-in cursor helper and initialize it with various parameters that control the operation of the cursor. The following code example shows the implementation of FetchByEnvelope:
[C#]
public IPlugInCursorHelper FetchByEnvelope(int ClassIndex, IEnvelope env, bool strictSearch, string WhereClause, object FieldMap)
{
if (this.DatasetType == esriDatasetType.esriDTTable)
  return null;

//The envelope that is passed in always has the same spatial reference as the data.
//For identify, it checks if search geometry intersects dataset bound
//but not ITable.Search(pSpatialQueryFilter, bRecycle), and so on.
//Check to see if the input envelope falls within the extent.
IEnvelope boundEnv = this.Bounds;
boundEnv.Project(env.SpatialReference);
if (boundEnv.IsEmpty)
  return null; //Or raise an error?
try
{
  SimplePointCursor spatialCursor = new SimplePointCursor(m_fullPath, 
                                                                                this.get_Fields(ClassIndex), 
                                                                                -1, 
                                                                                (System.Array)FieldMap, 
                                                                                 env, 
                                                                                 this.geometryTypeByID(ClassIndex));
  setMZ(spatialCursor, ClassIndex);

  return (IPlugInCursorHelper)spatialCursor;
}
catch (Exception ex)
{
  System.Diagnostics.Debug.WriteLine(ex.Message);
  return null;
}
}
[VB.NET]
Public Function FetchByEnvelope(ByVal ClassIndex As Integer, ByVal env As ESRI.ArcGIS.Geometry.IEnvelope, ByVal strictSearch As Boolean, ByVal WhereClause As String, ByVal FieldMap As Object) As ESRI.ArcGIS.Geodatabase.IPlugInCursorHelper Implements ESRI.ArcGIS.Geodatabase.IPlugInDatasetHelper.FetchByEnvelope
If Me.DatasetType = esriDatasetType.esriDTTable Then
   Return Nothing
End If

'The envelope that is passed in always has the same spatial reference as the data.
'For identify, it checks if search geometry intersects dataset bound
'but not ITable.Search(pSpatialQueryFilter, bRecycle), and so on.
'Check to see if input envelope falls within the extent.
Dim boundEnv As IEnvelope = Me.Bounds
boundEnv.Project(env.SpatialReference)
If boundEnv.IsEmpty Then
   Return Nothing 'Or raise an error?
End If

Try
   Dim spatialCursor As SimplePointCursor = New SimplePointCursor(m_fullPath, _
                                                                                         Me.Fields(ClassIndex), _
                                                                                         -1, _
                                                                                         CType(FieldMap, System.Array), _
                                                                                         env, _
                                                                                         Me.geometryTypeByID(ClassIndex))
   setMZ(spatialCursor, ClassIndex)
  
     Return CType(spatialCursor, IPlugInCursorHelper)
Catch ex As Exception
   System.Diagnostics.Debug.WriteLine(ex.Message)
   Return Nothing
End Try
End Function
Parameters required by the SimplePointCursor class (defined in the Simple point plug-in data source sample) are passed through the constructor. The following are these parameters:
  • Field map—Controls which attribute values are fetched by the cursor.
  • Query envelope—Determines which rows are fetched by the cursor.
  • File path—Tells the cursor where the data is.
The example ignores some of the FetchByEnvelope parameters, as ClassIndex applies only to feature classes within a feature dataset and WhereClause applies only to those data sources supporting ISQLSyntax. Also, strictSearch can be ignored since the example does not use a spatial index to perform its queries and always returns a cursor of features that strictly fall within the envelope.
There are other equally valid ways of implementing FetchByEnvelope, FetchByID, and FetchAll. Depending on the data source, it might be more appropriate to create the cursor helper, then use a postprocess to filter the rows to be returned.
There is one more member of IPlugInDatasetHelper that is worth mentioning. The Bounds property returns the geographic extent of the dataset. Many data sources have the extent recorded in a header file, in which case, implementing Bounds is easy. However, in the example, a cursor on the entire dataset must be opened and a minimum bounding rectangle gradually built. The implementation uses IPlugInCursorHelper. It would be unusual for another developer to consume the plug-in interfaces this way, since once the data source is implemented, the normal geodatabase interfaces work with it in a read-only manner. With the Bounds property, it is necessary to create an envelope or clone a cached envelope. Problems can occur with projections if a class caches the envelope and passes out pointers to the cached envelope.
The optional interface, IPlugInDatasetWorkspaceHelper2, has a FetchByFilter method. If a plug-in implements this interface, this method is called instead of FetchByEnvelope. FetchByFilter allows filtering by spatial envelope, WHERE clause, and a set of OIDs (FIDSet). See the following code example:
 
[C#]
public IPlugInCursorHelper FetchWithFilter (
    int ClassIndex,
    IEnvelope env,
    bool strictSearch,
    string whereClause,
    IFIDSet FIDSet,
    object FieldMap
);
[VB.NET]
Public Function FetchWithFilter ( _
    ByVal ClassIndex As Integer, _
    ByVal env As IEnvelope, _
    ByVal strictSearch As Boolean, _
    ByVal WhereClause As String, _
    ByVal FIDSet As IFIDSet, _
    ByVal FieldMap As Object _
) As IPlugInCursorHelper
This interface should be implemented like FetchByEnvelope, unless the FIDSet parameter is supplied. If the FIDSet is supplied, it contains a list of OIDs to return. The cursor should return only those rows whose OID is in the FIDSet and that match the spatial filter and WHERE clause.
A plug-in dataset helper should implement IPlugInFileSystemDataset if the data source is file based and multiple files make up a dataset. Single-file and folder-based data sources do not need to implement this interface.
A plug-in dataset helper should implement IPlugInRowCount if the RowCountIsCalculated property of the workspace helper returns false; otherwise, this interface should not be implemented. If this interface is implemented, make sure it operates quickly. It should be faster than just opening a cursor on the entire dataset and counting.
A plug-in dataset helper can also implement the optional interfaces IPlugInFileOperationsIPlugInFileOperationsClass, IPlugInIndexInfo, IPlugInIndexManager, and IPlugInLicense).
The IPlugInDatasetHelper2 interface can be implemented to optimize queries if there is a way to filter results from the data source more efficiently than checking the attributes of each row one-by-one. For example, data sources that have indexes should implement this interface to take advantage of indexing.

Implementing a plug-in cursor helper

The plug-in cursor helper deals with raw data and is normally the class that contains the most code. The cursor helper represents the results of a query on the dataset. The class must implement the IPlugInCursorHelper interface but does not need to be publicly creatable, because the plug-in dataset helper is responsible for creating it. See the following illustration:
The cursor position is advanced by NextRecord. In the example, a new line of text is read from the file and stored in a string. As described in the previous section, the dataset helper defines the way the cursor operates; this is reflected in the example's implementation of NextRecord. If a record is being fetched by the ObjectID, the cursor is advanced to that record. If a query envelope is specified, the cursor is moved to the next record with a geometry that falls within the envelope. See the following code example:
[C#]
public void NextRecord()
{
  if (m_bIsFinished) //Error has already thrown once.
     return;

  //OID search has been performed.
  if (m_iOID > -1 && m_sbuffer != null)
  {
     m_pStreamReader.Close();
     m_bIsFinished = true; 

       throw new COMException("End of SimplePoint Plugin cursor", E_FAIL);
  }
  else
  {
     //Read the file for text.
     m_sbuffer = ReadFile(m_pStreamReader, m_iOID);
     if (m_sbuffer == null)
     {
        //Finish reading and close the stream reader so resources will be released.
        m_pStreamReader.Close();
        m_bIsFinished = true; 

          //Raise E_FAIL to notify end of cursor.
       throw new COMException("End of SimplePoint Plugin cursor", E_FAIL);
     }

     //Search by envelope or return all records and let post-filtering do 
       //the work for you (performance overhead).
     else if (m_searchEnv != null && !(m_searchEnv.IsEmpty)) 
       {
        this.QueryShape(m_wkGeom);
        IRelationalOperator pRelOp = (IRelationalOperator)m_wkGeom; 
          if (!pRelOp.Disjoint((IGeometry)m_searchEnv))
           return; //Valid record within search geometry. Stop advancing.
        else
           this.NextRecord();
     }
}
[VB.NET]
Public Sub NextRecord() Implements IPlugInCursorHelper.NextRecord
   If m_bIsFinished Then 'Error has already thrown once.
      Return
   End If

   'OID search has been performed.
   If m_iOID > -1 AndAlso Not m_sbuffer Is Nothing Then
      m_pStreamReader.Close()
      m_bIsFinished = True

      Throw New COMException("End of SimplePoint Plugin cursor", E_FAIL)
   Else
      'Read the file for text.
      m_sbuffer = ReadFile(m_pStreamReader, m_iOID)
      If m_sbuffer Is Nothing Then
         'Finish reading and close the stream reader so resources will be released.
         m_pStreamReader.Close()
         m_bIsFinished = True

         'Raise E_FAIL to notify end of cursor.
         Throw New COMException("End of SimplePoint Plugin cursor", E_FAIL)
         'Search by envelope or return all records and let post-filtering do 
          'the work for you (performance overhead).
      ElseIf Not m_searchEnv Is Nothing AndAlso Not (m_searchEnv.IsEmpty) Then
         Me.QueryShape(m_wkGeom)
         Dim pRelOp As IRelationalOperator = CType(m_wkGeom, IRelationalOperator)
         If (Not pRelOp.Disjoint(CType(m_searchEnv, IGeometry))) Then
            Return 'Valid record within search geometry. Stop advancing.
         Else
            Me.NextRecord()
         End If
      End If
   End If
End Sub
Implementation of NextRecord must raise an error if there are no more rows to fetch.
The geometry of the feature should be returned by QueryShape. As with many other ArcObjects methods having names beginning with Query, the object to be returned is already instantiated in memory.
For this PlugInCursorHelper, only setting the coordinates of the point feature is needed. See the following code example:
[C#]
public void QueryShape(IGeometry pGeometry)
{
   if (pGeometry == null)
      return;
 
   try
   {
      double x, y;
      x = Convert.ToDouble(m_sbuffer.Substring(0, 6));
      y = Convert.ToDouble(m_sbuffer.Substring(6, 6));

      #region set M and Z aware
      if (m_bZ)
         ((IZAware)pGeometry).ZAware = true;
      if (m_bM)
         ((IMAware)pGeometry).MAware = true;
      #endregion

      //Insert (advanced) geometry construction.
      if (pGeometry is IPoint)
      {
         ((IPoint)pGeometry).PutCoords(x, y);
         if (m_bM)
            ((IPoint)pGeometry).M = m_iInterate;
         if (m_bZ)
            ((IPoint)pGeometry).Z = m_iInterate * 100;
      }
      else if (pGeometry is IPolyline) 
          buildPolyline((IPointCollection)pGeometry, x, y);
      else if (pGeometry is IPolygon)
         buildPolygon((IPointCollection)pGeometry, x, y);
      else
         pGeometry.SetEmpty();
   }
   catch (Exception ex)
   {
      System.Diagnostics.Debug.WriteLine(" Error: " + ex.Message);
      pGeometry.SetEmpty();
   }
}
[VB.NET]
Public Sub QueryShape(ByVal pGeometry As IGeometry) Implements IPlugInCursorHelper.QueryShape
  If pGeometry Is Nothing Then
     Return
  End If

  Try
     Dim x, y As Double
     x = Convert.ToDouble(m_sbuffer.Substring(0, 6))
     y = Convert.ToDouble(m_sbuffer.Substring(6, 6))

     ' #Region "set M and Z aware."
     If m_bZ Then
        CType(pGeometry, IZAware).ZAware = True
     End If
     If m_bM Then
        CType(pGeometry, IMAware).MAware = True
     End If
     ' #End Region.

     'Insert (advanced) geometry construction.
     If TypeOf pGeometry Is IPoint Then
        CType(pGeometry, IPoint).PutCoords(x, y)
        If m_bM Then
           CType(pGeometry, IPoint).M = m_iInterate
        End If
        If m_bZ Then
           CType(pGeometry, IPoint).Z = m_iInterate * 100
         End If
     ElseIf TypeOf pGeometry Is IPolyline Then
        buildPolyline(CType(pGeometry, IPointCollection), x, y)
     ElseIf TypeOf pGeometry Is IPolygon Then
        buildPolygon(CType(pGeometry, IPointCollection), x, y)
     Else
        pGeometry.SetEmpty()
     End If
  Catch ex As Exception
     System.Diagnostics.Debug.WriteLine(" Error: " & ex.Message)
     pGeometry.SetEmpty()
  End Try
End Sub
For data sources with complex geometries, the performance of QueryShape can be improved by using a shape buffer. Use IESRIShape.AttachToESRIShape to attach a shape buffer to the geometry. This buffer should then be reused for each geometry.
The ESRI white paper, ESRI Shapefile Technical Description, can be referenced for more information on shape buffers, since shapefiles use the same shape format.
The attributes of the current record are returned by QueryValues. The field map (specified when the cursor was created) dictates which attributes to fetch. This improves performance by reducing the amount of data transfer. For example, when features are being drawn on the map, it is likely that only a small subset, or even none of the attribute values, will be required. The return value of QueryValues is interpreted by ArcGIS as the ObjectID of the feature. See the following code example:
[C#]
public int QueryValues(IRowBuffer Row)
{
  try
  {
     if (m_sbuffer == null)
        return -1;

     for (int i = 0; i < m_fieldMap.GetLength(0); i++)
     {
        //Insert field map interpretation.
        if (m_fieldMap.GetValue(i).Equals(-1))
           continue;

        IField valField = m_fields.get_Field(i);
        char parse = m_sbuffer[m_sbuffer.Length - 1];
        switch (valField.Type)
        {
           case esriFieldType.esriFieldTypeInteger:
           case esriFieldType.esriFieldTypeDouble:
           case esriFieldType.esriFieldTypeSmallInteger:
           case esriFieldType.esriFieldTypeSingle:
              Row.set_Value(i, Convert.ToInt32(parse)); //Get ASCII code number for the character.
              break;
           case esriFieldType.esriFieldTypeString:
              Row.set_Value(i, parse.ToString());
              break;
        }
     }
     return m_iInterate; //HIGHLIGHT: 2.3 QueryValues. Return OID.
  }
  catch (Exception ex)
  {
     System.Diagnostics.Debug.WriteLine(ex.Message);
     return -1;
  }
}
[VB.NET]
Public Function QueryValues(ByVal Row As IRowBuffer) As Integer Implements IPlugInCursorHelper.QueryValues
  Try
     If m_sbuffer Is Nothing Then
        Return -1
     End If

     Dim i As Integer = 0
     Do While i < m_fieldMap.GetLength(0)
        'Insert field map interpretation.
        If m_fieldMap.GetValue(i).Equals(-1) Then
           i += 1
           Continue Do
        End If

        Dim valField As IField = m_fields.Field(i)
        Dim parse As Char = m_sbuffer.Chars(m_sbuffer.Length - 1)
        Select Case valField.Type
           Case esriFieldType.esriFieldTypeInteger, esriFieldType.esriFieldTypeDouble, esriFieldType.esriFieldTypeSmallInteger, esriFieldType.esriFieldTypeSingle
              Row.Value(i) = Convert.ToInt32(parse) 'Get ASCII code number for the character.
           Case esriFieldType.esriFieldTypeString
              Row.Value(i) = parse.ToString()
        End Select
        i += 1
    Loop
    Return m_iInterate 'Return OID.
  Catch ex As Exception
     System.Diagnostics.Debug.WriteLine(ex.Message)
     Return -1
  End Try
End Function


See Also:

Sample: Simple point plug-in data source
Plug-in data sources
Implementing optional functionality on plug-in data sources
Adding a plug-in data source programmatically
ESRIRegAsm utility




Development licensing Deployment licensing
ArcGIS for Desktop Basic ArcGIS for Desktop Basic
ArcGIS for Desktop Standard ArcGIS for Desktop Standard
ArcGIS for Desktop Advanced ArcGIS for Desktop Advanced
ArcReader