Search code examples
c++excelxll

How to return an array from an XLL UDF


I'm trying to write an array constructor for Excel as a worksheet function using the C API.

Goal: =array_cons(1, 2, 3) => {1, 2, 3}

However, I am not initializing the XLOPER12 correctly. In Excel, my function currently returns #NUM. I am taking the argument list and packing it into a vargs array via macros then trying to return the part of the array that was supplied.

#include <windows.h>
#include <xlcall.h>
#include <framewrk.h>
#include <boost/preprocessor.hpp>

#define VARG_COUNT 250
#define VARG_FORMAT(Z, A, B) B##A, 
#define VARG_DEF_LIST(N) BOOST_PP_REPEAT(N, VARG_FORMAT, LPXLOPER12 varg) \
                         LPXLOPER12 varg##N
#define VARG_ARRAY(N) { BOOST_PP_REPEAT(N, VARG_FORMAT, varg) varg##N }
#define GET_VARGS VARG_ARRAY(VARG_COUNT)

__declspec(dllexport) LPXLOPER12 WINAPI array_cons(VARG_DEF_LIST(VARG_COUNT))
{
    LPXLOPER12 vargs[] = GET_VARGS;
    int args_passed = 0;
    for(int i = 0; i < VARG_COUNT; ++i, ++args_passed)
    {
        if (vargs[i]->xltype == xltypeMissing)
        {
            break;
        }
    }
    if (args_passed == 0)
    {
        XLOPER12 err;
        err.xltype = xltypeErr;
        err.val.err = xlerrValue;
        return (LPXLOPER12)&err;
    }
    XLOPER12 list;
    list.xltype = xltypeMulti;
    list.val.array.lparray = (XLOPER12*)vargs;
    list.val.array.rows = args_passed;
    list.val.array.columns = 1;
    return (LPXLOPER12)&list;
}

Solution

  • I figured it out. A couple of things to note here -

    You need to make sure that your UDF registration is using the right signature. In my case, I wanted Excel references to give me their respective values, so I used the Q type when registering the function. If you don't understand this, check out http://msdn.microsoft.com/en-us/library/office/bb687869.aspx

    In order to return an array, you have to dynamically allocate new memory to the list.val.array.lparray member and iteratively populate it.

    __declspec(dllexport) LPXLOPER12 WINAPI array_cons(VARG_DEF_LIST(VARG_MAX)) {
        LPXLOPER12 vargs[] = GET_VARGS;
        int args_passed = 0;
        for(int i = 0; i < VARG_MAX; ++i, ++args_passed) {
            if (vargs[i]->xltype == xltypeMissing) {
                break;
            }
        }
        XLOPER12 list;
        list.xltype = xltypeMulti | xlbitDLLFree;
        list.val.array.lparray = new XLOPER12[args_passed];
        list.val.array.rows = args_passed;
        list.val.array.columns = 1;
        for(int i = 0; i < args_passed; ++i) {
            list.val.array.lparray[i] = *vargs[i];
        }
        return &list;
    }
    

    Since we are dynamically allocating memory, we need to define the callback to free it.

    __declspec(dllexport) void WINAPI xlAutoFree12(LPXLOPER12 p) {
        if (p->xltype == (xltypeMulti | xlbitDLLFree)) {
            delete [] p->val.array.lparray;
        }
    }