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.

In this topic


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
ArcView ArcView
ArcEditor ArcEditor
ArcInfo ArcInfo
ArcReader