How to return an array from an XLL UDF

本小妞迷上赌 提交于 2019-12-12 08:59:27

问题


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

回答1:


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



回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/14301998/how-to-return-an-array-from-an-xll-udf

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!