| Entering data: Drop-down lists |
GS-Calc (ver. 17 and later) can be configured to use functions written by users in C/C++ as plain DLL libraries. Such functions can be even used as a replacement for the default built-in functions. They can accept and return all the argument types and additionally they can also "return" images displayed directly in worksheets or messages displayed after each update. What's important, using these functions GS-Calc retains the complete multicore support, the same speed and memory requirements (e.g. no extra arrays are allocated when passing ranges as arguments). You can add virtually any number of such functions.
Libraries are added by the Settings > Imported Function Libraries command as shown on the screenshot below. The internal function names MUST match the original C/C++ names. The displayed names are what is entered in worksheet cells and the only requirement is that they must be unique.

The added functions must be declared as
typedef double(__cdecl *DLL_EXT_FUNCTION)(GSCalcArg *arg);
where GSCalcArg is a structure enabling you to pass up to 15 arguments: numbers, strings and ranges/arrays.
struct ArrayItem
{
uint8_t type; // DLL_ARGT_EMPTY | DLL_ARGT_DOUBLE | DLL_ARGT_TEXT
uint8_t err_code; // ERROR1_DIV_BY_ZERO...ERROR1_SYNTAX_ERROR
uint16_t col; // [0, 4095]
uint32_t row; // [0, 12582911]
double num; // numeric cell
char text[MAXINPUT + 1];// text cell; max. 1024+NULL char. utf-8 string
};
typedef int(__cdecl *READ_ARRAY)(void *env, int array_index, ArrayItem *val);
typedef int(__cdecl *WRITE_ARRAY)(void *env, int array_index, ArrayItem *val);
typedef void* (__cdecl *MALLOC)(size_t s);
typedef void(__cdecl *MFREE)(void *_Memory);
struct GSCalcArg
{
uint8_t types[16]; // input: DLL_ARGT_NUMBER | DLL_ARGT_TEXT | DLL_ARGT_ARRAY | DLL_ARGT_EMPTY
// output: as above + DLL_ARGT_IMAGE | DLL_ARGT_MESSAGE
uint8_t errors[16]; // ERROR1_DIV_BY_ZERO...ERROR1_SYNTAX_ERROR
double numbers[16];
char *strings[16]; // input (UTF-8) 0...14 strings are preallocated by GS-Calc and must not be overwritten;
// strings[15] is used for output strings and already points to a MAXINPUT+1 char. temp. buffer
struct Dims
{
uint32_t cx;
uint32_t cy;
} array_dims[16]; // dimensions of the subsequent and grouped together range/array arguments passed from GS-Calc
struct
{
BYTE *data; // DIB data if you return types[15] = DLL_ARGT_IMAGE; must be (de-)allocated with memory_alloc/memory_free
size_t size; // DIB data size
Dims dims; // optional resizing when displaying the image (returned either as a DIB or a file path)
MALLOC memory_alloc;
MFREE memory_free;
} image;
READ_ARRAY read_array; // returns ArrayItem.type
WRITE_ARRAY write_array; // returns ArrayItem.type or -1 if the out-of-memory condition occurs
void *env; // internal data that must be passed back in read_array/write_array calls
};
Indices 0...14 in types[] and errors[] in GSCalcArg represent all subsequent argument types and errors from left to right passed from GS-Calc.
Indices 0...14 in numbers[], strings[] and array_dims[] point to argument values. The original parameters are already divided into these 3 groups (numbers, strings and ranges/arrays) when your functions is called. For example, if some worksheet cell execute your function
my_function(1.0, 2, "abc", a5:b15,)
the parameters passed to the DLL in the GSCalcArg structure will be as follows:
types[0] = DLL_ARGT_DOUBLE, types[1] = DLL_ARGT_DOUBLE, types[2] = DLL_ARGT_TEXT, types[3] = DLL_ARGT_ARRAY, types[4] = DLL_ARGT_EMPTY
numbers[0] = 1, numbers[1] = 2, strings[0] = "abc", array_dims[0] = {2, 10}
You can access numbers and strings directly. Array elements can be read (and write on output) using the read_array() (and write_array())
functions as shown below in the filter function example. The first argument is the "env" internal pointer from the GSCalcArg structure, the 2nd is the array index (0-14) as passed in array_dims[] and the last is a pointer to the ArrayItem structure with the "col" and "row" of the requested element.
Note: for very large array, for best possible performance while retrieving data from data from arrays or writing data to arrays, subsequent read_array() (and write_array()) calls MUST access the array elements in the left-to-right and top-to-bottom order.
On output, the index 15 (DLL_ARGC_RET) is used to specify the returned type (in types[15]), possible error code (in errors[15]) and value
(in numbers[15], strings[15] or array_dims[15]). The values types[15] and errors[15] MUST always be set correctly on return.
Note: the strings[15] already contain a valid buffer (up to 1024 characters + NULL) allocated by GS-Base and if a given function
returns a text value, it must be copied to that buffer as a NULL terminated string.
Examples:
// messageIf(condition, text)
//
// arguments:
// condition, message text
// returns:
// if condition != 0, 'messageIf' displays 'text' as a message box after each update/recalculation
// otherwise the message text is displayed in a cell as normal text
__declspec(dllexport) double __cdecl messageIf(GSCalcArg *arg)
{
uint8_t errorCode = 0;
if (arg->types[0] != DLL_ARGT_DOUBLE && arg->types[0] != DLL_ARGT_EMPTY || arg->types[1] != DLL_ARGT_TEXT)
errorCode = ERROR1_INVALID_VALUE;
if (arg->types[2] != DLL_ARGT_NONE) // more than 2 arguments detected
errorCode = ERROR1_SYNTAX_ERROR;
if (!errorCode && arg->errors[0])
errorCode = arg->errors[0];
if (!errorCode && arg->errors[1])
errorCode = arg->errors[1];
if (!errorCode)
{
::strncpy(arg->strings[DLL_ARGC_RET], arg->strings[0], 1024);
arg->strings[DLL_ARGC_RET][1024] = 0;
return arg->types[DLL_ARGC_RET] = (arg->numbers[0] ? DLL_ARGT_MESSAGE : DLL_ARGT_TEXT);
}
else
{
arg->errors[DLL_ARGC_RET] = errorCode;
return arg->types[DLL_ARGC_RET] = DLL_ARGT_EMPTY;
}
return 0;
}
// filter(range, column, number)
//
// arguments:
// range - a range/array
// column - column index (from 0 to the number of columns in 'range' - 1)
// number - numeric value to perform equality check
// returns:
// an array consisting of rows of 'range' containing 'number' in the specified column
__declspec(dllexport) double __cdecl filter(GSCalcArg *arg)
{
uint8_t errorCode = 0;
if (arg->types[0] != DLL_ARGT_ARRAY || arg->types[1] != DLL_ARGT_DOUBLE || arg->types[2] != DLL_ARGT_DOUBLE)
errorCode = ERROR1_INVALID_VALUE;
if (arg->types[3] != DLL_ARGT_NONE)
errorCode = ERROR1_SYNTAX_ERROR;
if (!errorCode && arg->numbers[0] >= arg->array_dims[0].cx)
errorCode = ERROR1_INVALID_VALUE;
if (!errorCode && arg->errors[0])
errorCode = arg->errors[0];
if (!errorCode && arg->errors[1])
errorCode = arg->errors[1];
if (!errorCode && arg->errors[2])
errorCode = arg->errors[2];
ArrayItem x = { 0 };
int outRow = 0;
for (uint32_t inRow = 0; inRow < arg->array_dims[0].cy && !errorCode; ++inRow)
{
x.row = inRow;
x.col = static_cast<uint16_t>(arg->numbers[0]);
if (arg->read_array(arg->env, 0, &x) == -1)
{
errorCode = x.err_code;
break;
}
bool match = false;
if (x.type == DLL_ARGT_DOUBLE)
match = (x.num == arg->numbers[1] || x.err_code && x.err_code == arg->errors[2]);
else if (x.type == DLL_ARGT_EMPTY)
match = (arg->numbers[1] == 0);
else
errorCode = ERROR1_INVALID_VALUE;
if (match)
{
for (x.col = 0; x.col < arg->array_dims[0].cx && !errorCode; ++x.col)
{
x.row = inRow;
arg->read_array(arg->env, 0, &x);
x.row = outRow;
// for best performance when writing to very large arrays, try to write in the left-to-right and top-to-bottom order
if (arg->write_array(arg->env, DLL_ARGC_RET, &x) == -1) // -1 means the out-of-memory condition or an invalid row/column
errorCode = x.err_code;
}
++outRow;
}
}
arg->errors[DLL_ARGC_RET] = (!outRow ? ERROR1_NULL_VALUE : 0);
return arg->types[DLL_ARGC_RET] = DLL_ARGT_ARRAY;
return 0;
}
// image(path, cx, cy)
//
// arguments:
// path - a bmp, jpg, png, gif file path
// cx/cy - display image size (optional)
// returns:
// loads an image (bmp, jpg, png, gif) from a disk and displays it, optionally resizing it
// to the cx/cy dimensions (in screen pixels)
__declspec(dllexport) double __cdecl image(GSCalcArg *arg)
{
uint8_t errorCode = 0;
if (arg->types[0] != DLL_ARGT_TEXT ||
arg->types[1] != DLL_ARGT_DOUBLE && arg->types[1] != DLL_ARGT_EMPTY ||
arg->types[2] != DLL_ARGT_DOUBLE && arg->types[2] != DLL_ARGT_EMPTY)
errorCode = ERROR1_INVALID_VALUE;
if (arg->types[3] != DLL_ARGT_NONE)
errorCode = ERROR1_SYNTAX_ERROR;
if (!errorCode && arg->errors[0])
errorCode = arg->errors[0];
::strcpy_s(arg->strings[DLL_ARGC_RET], MAXINPUT, arg->strings[0]);
arg->image.dims.cx = static_cast<uint32_t>(arg->numbers[0]);
arg->image.dims.cy = static_cast<uint32_t>(arg->numbers[1]);
// arg->image.data must remain NULL
arg->errors[DLL_ARGC_RET] = errorCode;
return arg->types[DLL_ARGC_RET] = DLL_ARGT_IMAGE;
}
Complete gsclib.h header file that must be included in each DLL project
A sample complete project with the messageIf, filter, image and fractal functions: