What's the use case? I think it's better to keep independent tasks separate.

Anyway:

1. Create your own task class by extending %SYS.Task.Definition.

2. Program your task

  • Specify TaskName parameter to provide readable task name
  • Add class properties if needed - they are task arguments
  • Override OnTask method to program your logic

3. Create a new task, choosing your task class (by TaskName).

You need to specify where you want to send your request. Something like this should work.

Method OnD03Alert(req As User.Alert, Output resp As Ens.StreamContainer) As %Status
{
    #Dim sc As %Status = $$$OK      
    #Dim test As %Integer = 0
    
    Set httprequest = ##class(%Net.HttpRequest).%New()   
    Set httprequest.Server =  "www.usamobility.net"
    Set httprequest.Location = "cgi-bin/wwwpage.exe"
    
    Do httprequest.SetParam("PIN", ..PIN)
    Do httprequest.SetParam("MSSG", "motogplay")
    
    Set sc = httprequest.Post(, test, $$$NO)
    
    If $$$ISOK(sc) {
        Set stream = httprequest.HttpResponse.Data
        Set resp = ##class(Ens.StreamContainer).%New(stream)
    }
    Quit sc
}

Also Ensemble operations must return persistent objects so I replaced string with Ens.StreamResponse.

Why do you want to achieve that?

You need custom datatype for that, defining your parameters:

Class Testing.String Extends %String
{
Parameter myPropName;
}

And use that as a property type:

Class Testing.PropertyParameters Extends %RegisteredObject
{
Property p1 As Testing.String(MAXLEN = 5, myPropName = "myPropValue");
}

Still, please tell us your use case.

Working code:

int GetGlobalOrder(char *global, int start, int end, CACHE_EXSTRP result)
{
    if (isInitialized == false) {
        Initialize(NULL);
    }

    // narg Number of subscript expressions pushed onto the argument stack.
    int narg = 1;

    // Direction for the $Order is 1 for forward, -1 for reverse.
    int dir = 1;
    // Indicates whether the data value, if there is one, should be returned.
    int valueflag = 1;

    // Has argument flag
    int flag = 0;

    // key - current subscript
    int key = start - 1;
    
    // row count
    int c=0;

    while (key<end) {
        CACHEPUSHGLOBAL(strlen(global), global);
        CACHEPUSHINT(key);
        CACHEGLOBALORDER(narg,  dir, valueflag);

        CACHEPOPINT(&flag);
        if (flag) {
          //use CACHETYPE() to get the value type, then the appropriate CACHEPOP for the subscript
          CACHEEXSTRKILL(result);
          CACHEPOPEXSTR(result);
          // do stuff with result
        }
        //use CACHETYPE() to get the subscript type, then the appropriate CACHEPOP for the subscript
        int type = CACHETYPE();

        CACHEPOPINT(&key);

        if (key==NULL) {
            break;
        }

        c++;
    }
    return ZF_SUCCESS;
}

Thanks to  @Chuck Sorenson for help!

Here's a sample C code to iterate over $lb structure.

#include <math.h>

/// Convert unsigned integer bytes to integer
int64_t makeint(const char *buff, size_t offset, size_t offsetinint, size_t len)
{
    union
    {
        int64_t i64;
        uint8_t u8[8];
    }d64;

    offsetinint = offsetinint & 7;
    memset(&d64, 0, sizeof(d64));

    memcpy(&d64.u8[offsetinint], buff + offset, len > (8 - offsetinint) ? (8 - offsetinint) : len);
    return d64.i64;
}

/// get next power of 2 greater than v
int64_t next2(int64_t v)
{
    v--;
    v |= v >> 1;
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;
    v |= v >> 32;
    v++;
    return v;
}

/// List types. NONE and NONE2 are placeholders
/// See %CACHE_HOME%\dev\Cache\callout\demo\czf.pdf (Section "Lists")
enum ListTypes {NONE, STRING, USTRING, NONE2, INTP, INTN, DOUBLEP, DOUBLEN, FLOAT};

/// This function iterates over all elements in a list
void ListToTuple(CACHE_EXSTRP result)
{
    // $lb structure
    char* list = result->str.ch;
    int listLength = result->len;

    // current element
    int num = 0;

    // current byte position
    int i=0;

    // length of current element
    int l = 0;

    // datatype
    int type = 0;

    while (i<listLength) {

        // Calculate length of current element - START
        if (0 == (l = (list[i]&255))) {

            // First BYTE is 0, length is in following 2 BYTEs
            size_t t_n = ((list[i+1]&255)|((list[i+2]&255)<<8));
            if (t_n != 0) {
                  l = t_n + 3;
            } else {
                // 4 Byte length
                l = ((list[i+3]&255) | ((list[i+4]&255) << 8) | ((list[i+5]&255) << 16) | ((list[i+6]&255) << 24)) + 7;
            }
        }
        // Calculate length of current element - END

        // Calculate data position - START
        int dataStart = 0;
        int dataLength = 0;

        if (l < 255) {
            type = list[i+1];
            dataStart = i + 2;
            dataLength = l - 2;
        } else if (l < 65536) {
            type = list[i+3];
            dataStart = i + 4;
            dataLength = l - 4;
        } else {
            type = list[i+7];
            dataStart = i + 8;
            dataLength = l - 8;
        }
        // Calculate data position - END
        
        if (type==STRING) {
            char* value;
            memcpy(value, list+dataStart, dataLength);
        } else if (type==USTRING) {
            char* value;
            memcpy(value, list+dataStart, dataLength/2);
        } else if (type == INTP) {
            int64_t value = makeint(list, dataStart, 0, dataLength);
        } else if (type==INTN) {
            int64_t value = 0;
            if (l==2) {
                value = -1;
            } else {
                memcpy(&value, list+dataStart, dataLength);
                if (value == 0) {
                    value = - (1 << (dataLength * 8));
                } else {
                    int64_t pow2 = next2(value);
                    value = value - pow2;
                }
            }
        } else if (type==DOUBLEP) {
            int64_t temp = makeint(list, dataStart+1, 0, dataLength-1);
            signed char exp = list[dataStart];
            double value = temp*pow(10, exp);
        } else if (type==DOUBLEN) {
            int64_t temp = 0;
            memcpy(&temp, list+dataStart+1, dataLength-1);
            if (temp == 0) {
                temp = - (1 << (dataLength * 8));
            } else {
                int64_t pow2 = next2(temp);
                temp = temp - pow2;
            }
            signed char exp = list[dataStart];
            double value = temp*pow(10, exp);
        } else if (type==FLOAT) {
            double value;
            memcpy(&value, list+dataStart, 8);
        }
        
        i += l;
        num++;
    }
}

I would like to advise against this course of action, there are several reasons:

  1. Ensemble messages should be as small as possible. If you can pass id only, pass id only, etc. Minimizing message size keeps production efficient.
  2. ResultSet does not contain the data itself, it's more like a compiled instructions on how to get the data. Only when you're calling Next method the data is actually loaded into memory
  3. Results of SQL query should be immediately acted upon, because the older the SQL results, the more they differ with reality. it is best to pass parameters and execute query in-place.

If you want it for debugging purposes you can add a trace event which would store ids only for example, but if you actually need to pass a set of results between different business hosts it may be beneficial to you to rethink the production architecture.

Here's some questions:

  1. What business hosts do you have?
  2. What data do you pass between different hosts?
  3. Can you pass parameters between hosts and execute a query on a target host?
  4. Can you pass not a set between hosts, but rather elements individually?

Some options:

> - Utilize another language or library that has methods to parse the data into an Excel file (https://stackoverflow.com/questions/17684610/python-convert-csv-to-xlsx).

If you want to use Python from Cache, check PythonGateway.

Mimedata is subscripted by name and index.

So in your case:

set name = "BulkFileUpload"
for i=1:1:%request.CountMimeData(name)
    set mimeData = %request.GetMimeData(name, , i)
}

On each iteration mimeData variable would hold the stream with one next mimedata.

%request is simply an object of %CSP.Request class, check the docs or code to know how it works.

Additionally you can use this snippet to see what's inside %request, %response and %session objects:

set %response.ContentType = "html"
do ##class(%CSP.Utils).DisplayAllObjects()
quit $$$OK