Home » excel » c++ – How to return an array from an XLL UDF

c++ – How to return an array from an XLL UDF

Posted by: admin April 23, 2020 Leave a comment

Questions:

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;
}
How to&Answers:

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;
    }
}

Answer:

Your solution is incomplete.

You write

XLOPER12 list;

in the body of the function. This makes “list” a local variable that is created on the stack. When the function returns, “list” goes out of scope. You’re returning a pointer to a variable that is no longer in scope, and when Excel attempts to access this pointer, the behavior is undefined. If Excel happens to call another function before processing the return value, your list variable will get clobbered.

The solution is that you either have to dynamically allocate memory for list (and then make sure it gets freed later) or make list a static or global variable.

Answer:

Not sure why you think the xll library won’t allow you to develop an open source add-in. IANAL, but the Ms-PL seems to allow that. Have you looked at http://xllfunctional.codeplex.com/? It uses similar non-portable tricks that don’t drag boost in to the solution. The WINAPI/__stdcall is what it is. If you know how things happen down to the silicon you can take advantage of that.