Passing COBOL Data to Methods or Properties as SAFEARRAYs

When programming with ActiveX controls and COM objects, you can pass one- or two-dimensional COBOL tables to methods or properties that expect SAFEARRAY parameters. The runtime automatically converts a one- or two-dimensional COBOL table to a COM SAFEARRAY, as long as it contains only one elementary item that is USAGE HANDLE or USAGE HANDLE OF VARIANT.

The COM SAFEARRAY data type can contain elements of any type. Therefore, you must convert your COBOL data into variant type data before adding it to the array. Use the C$SETVARIANT library routine to create a new variant that stores the data if the initial value of the handle item passed to it is LOW-VALUES or SPACES. You need to free this variant using the DESTROY verb.

To use SAFEARRAYs, you should do the following:

  1. Declare a table in Working-Storage that has one or two OCCURS clauses. If specified, the second OCCURS clause must be on an item that is subordinate to the item with the first OCCURS clause. The table must contain only one elementary item that is USAGE HANDLE or USAGE HANDLE OF VARIANT. (OF VARIANT is optional but makes the code more readable.)
  2. Call C$SETVARIANT for each handle item in the table to convert the COBOL data to variant type data.
  3. Use the name of this table wherever a property or method requires a SAFEARRAY.

For example, Microsoft Chart Control has a property called ChartData. The value of this property is a SAFEARRAY. Each element of the array is a data point value for the chart.

01  myTable.
    03  filler occurs 5 times.
        05  chart-data     usage handle of variant.

screen section.
01  screen-1.
    03  mschart-1 
        MSChart
        line 3, column 5, size 50, lines 16.

    03  my-button push-button, "E&xit Program",
        ok-button,
        line 32, cline 23, column 27, size 13.

procedure division.
Main-Logic.

    perform varying col-number from 1 by 1 
      until col-number > 5
        call "c$setvariant" 
            using col-number, chart-data(col-number)
    end-perform.

    display standard graphical window, 
        title "ActiveX Table MSChart Sample - tblchart.cbl"
        lines 37, size 66, background-low.

    display screen-1.

    modify mschart-1
           ChartData = myTable.
 
    perform, with test after, until exit-button-pushed
        accept screen-1
    end-perform.

    perform varying col-number from 1 by 1 
       until col-number > 5
        destroy chart-data(col-number)
    end-perform.
    destroy screen-1.
    stop run.

Notice that the initial values of the chart-data table elements are spaces. When C$SETVARIANT is called with the chart-data items set to spaces, it creates new variant handles and sets the chart-data item to the variant handle.

The DESTROY statement destroys these handles and releases the associated memory. The DESTROY statement also sets the chart-data item to low-values to allow multiple destroys of the same handle item without any negative effects.

The following code is an example of a table with two OCCURS clauses passed as a two-dimensional SAFEARRAY to the ChartData property. Microsoft Chart Control takes the "string" elements of this array to be the x-axis labels and the numeric elements to be two series of chart data.

77  col-label       pic x(20).
77  series-2-data   pic 99.

01  myTable.
    03  filler occurs 5 times.
        05  filler occurs 3 times.
            07  chart-data   usage handle of variant.

screen section.
01  screen-1.
    03  mschart-1 
        MSChart
        line 3, column 5, size 50, lines 16.

    03  my-button push-button, "E&xit Program",
        ok-button,
        line 32, cline 23, column 27, size 13.

procedure division.
Main-Logic.

    perform varying col-number from 1 by 1 
       until col-number > 5
        string "Label " delimited by size
            col-number delimited by size 
            into col-label
        call "c$setvariant" 
            using col-label, chart-data(col-number,1)
        call "c$setvariant" 
            using col-number, chart-data(col-number,2)
        multiply col-number by 2 giving series-2-data
        call "c$setvariant" 
            using series-2-data, chart-data(col-number,3)
    end-perform.

    display standard graphical window, 
        title "ActiveX Table MSChart Sample - tblchart.cbl"
        lines 37, size 66, background-low.

    display screen-1.

    modify mschart-1
           ChartData = myTable.

    perform, with test after, until exit-button-pushed
        accept screen-1
    end-perform.

    perform varying col-number from 1 by 1 
       until col-number > 5
        destroy chart-data(col-number,1)
        destroy chart-data(col-number,2)
        destroy chart-data(col-number,3)
    end-perform.
    destroy screen-1.
    stop run.

Some ActiveX and COM objects that use SAFEARRAYs accept that some items in the array may be empty (for example, Microsoft ADO). Items that are empty are normally passed using the VT_EMPTY variant type.

If an element in an array that you want to pass is optional (i.e., it may sometimes be empty), you must tell the runtime this by coding one of the following:

01 VariantParam.
   03 VariantTable usage handle of variant occurs 5.

   call "c$setvariant" USING x"00" VariantTable(1)

or

   call "c$setvariant" USING "" VariantTable(1)

This tells the runtime to convert the content of VariantTable(1) to VT_EMPTY. If you use the latter approach, you may receive and ignore compiler warnings that an empty literal was encountered.