从 DLL 或 XLL 调用 Excel

适用于:Excel 2013 | Office 2013 | Visual Studio

借助 Microsoft Excel,DLL 可访问内置的 Excel 命令、工作表函数和宏表函数。 可按两种方式使用它们:通过从 Visual Basic for Applications (VBA) 调用的 DLL 命令和函数,以及通过 Excel 直接调用的已注册的 XLL 命令和函数。

Excel4、Excel4v、Excel12 和 Excel12v 函数

借助 Excel,DLL 可通过回调函数 Excel4Excel4vExcel12Excel12v 访问命令和函数。

Excel 版本 4 中引入了 Excel4Excel4v 函数。 它们适用于 XLOPER 数据结构。 Excel 2007 引入了两个新的回调函数 Excel12Excel12v,它们适用于 XLOPER12 数据结构。 Excel4Excel4v 函数由 Xlcall32.lib 库导出,后者必须包含在 DLL 或 XLL 项目中。 Excel12Excel12v 包含在 SDK C++ 源文件 Xlcall.cpp 中;如果想要通过 XLOPER12 结构访问 Excel 功能,则必须在项目中包含此文件。

以下代码显示了上述 4 个函数的函数原型。 前三个参数相同,只是第二个参数是一个指针,它同时指向第一对中的 XLOPER 和第二对中的 XLOPER12Excel4Excel12 中的调用约定是 _cdecl,它允许变量参数列表。 省略号表示指向 Excel4XLOPER 值和 Excel12XLOPER12 值的指针。 指针数量等于 count 参数的值。

Excel 的所有版本

int _cdecl Excel4(int xlfn, LPXLOPER operRes, int count,... );

int pascal Excel4v(int xlfn, LPXLOPER operRes, int count, LPXLOPER opers[]);

自 Excel 2007 起

int _cdecl Excel12(int xlfn, LPXLOPER12 operRes, int count,... );

int pascal Excel12v(int xlfn, LPXLOPER12 operRes, int count, LPXLOPER12 opers[]);

要让 DLL 能够调用 Excel4Excel4vExcel12Excel12v,Excel 必须将控制权传递给 DLL。 这意味着,仅可在以下情况下调用 C API 回调:

  • 从 Excel 直接调用或通过 VBA 调用的 XLL 命令内部。

  • 从 Excel 直接调用或通过 VBA 调用的 XLL 工作表或宏表函数内部。

不可在以下情况下调用 Excel C API:

  • 通过操作系统事件(例如,通过 DllMain 函数)。

  • 通过 DLL 创建的后台线程。

返回值

上述 4 个函数均返回一个整数值,它通知调用方是否成功调用函数或命令。 返回的值可为下述任一值:

返回值 在 Xlcall.h 中定义为 说明
0 xlretSuccess 函数或命令已成功执行。 这并非表示操作不出错。 例如,Excel4 在调用 FIND 函数时可能会返回 xlretSuccess,即使它的计算结果为 #VALUE!,因为找不到搜索文本。 应检查所返回的 XLOPER/XLOPER12 的类型和值(若可能)。
1 xlretAbort 用户已通过单击“取消”按钮或按 Esc 键停止命令宏。
2 xlretInvXlfn 所提供的函数或命令代码无效。 当调用函数无权调用函数或命令时,可能出现此错误。 例如,工作表函数无法调用宏表信息函数或命令函数。
4 xlretInvCount 调用中提供的参数数目不正确。
8 xlretInvXloper 一个或多个参数 XLOPERXLOPER12 值构建或填充错误。
16 xlretStackOvfl Excel 检测到存在操作可能溢出其堆栈的风险,因此未调用函数。
32 xlretFailed 命令或函数因任一其他返回值未描述的原因而失败。 例如,需要过多内存的操作会失败且出现此错误。 在尝试通过 xlCoerce 函数将较大型引用转换为 xltypeMulti 数组时,可能发生此问题。
64 xlretUncalced 操作尝试检索未计算的单元格的值。 要在 Excel 中保留重算完整性,工作表函数不可执行此操作。 但是,注册为宏表函数的 XLL 命令和函数可访问未计算的单元格值。
128 xlretNotThreadSafe (自 Excel 2007 起)注册为线程安全的 XLL 工作表函数尝试调用非线程安全的 C API 函数。 例如,线程安全函数无法调用 XLM 函数 xlfGetCell
256 xlRetInvAsynchronousContext (自 Excel 2010 起)异步函数句柄无效。
512 xlretNotClusterSafe (自 Excel 2010 起)群集上不支持此调用。

如果函数返回表中的某一失败值(即,不返回 xlretSuccess),则 XLOPERXLOPER12 返回值也将设置为 #VALUE!。 在某些情况下,检查此项可能足以测试是否成功,但应注意,调用可同时返回 xlretSuccess#VALUE!

如果对 C API 的调用导致 xlretUncalcedxlretAbort,则 DLL 或 XLL 代码应将控制权归还给 Excel,然后再进行任何其他 C API 调用(而不是调用 xlfree 函数来释放 XLOPERXLOPER12 值中由 Excel 分配的内存资源。

命令或函数枚举参数:xlfn

xlfn 参数是回调函数的第一个参数,它是一个 32 位带符号整数。 其值应为 SDK 头文件 Xlcall.h 中定义的函数或命令枚举之一,如下例中所示。

// Excel function numbers. 
#define xlfCount 0
#define xlfIsna 2
#define xlfIserror 3
#define xlfSum 4
#define xlfAverage 5
#define xlfMin 6
#define xlfMax 7
#define xlfRow 8
#define xlfColumn 9
#define xlfNa 10
...
// Excel command numbers. 
#define xlcBeep (0 | xlCommand)
#define xlcOpen (1 | xlCommand)
#define xlcOpenLinks (2 | xlCommand)
#define xlcCloseAll (3 | xlCommand)
#define xlcSave (4 | xlCommand)
#define xlcSaveAs (5 | xlCommand)
#define xlcFileDelete (6 | xlCommand)
#define xlcPageSetup (7 | xlCommand)
#define xlcPrint (8 | xlCommand)
#define xlcPrinterSetup (9 | xlCommand)
...

所有工作表和宏表函数均介于 0 (xlfCount) 至 0x0fff 十六进制数之间的范围,但 Excel 2013 中分配的最大数为 547 小数、0x0223 十六进制数 (xlfFloor_precise)。

所有命令函数均介于 0x8000 十六进制数 (xlcBeep) 至 0x8fff 十六进制数,但 Excel 2013 中分配的最大数为 0x8328 十六进制数 (xlcHideallInkannots)。 这些在头文件中定义为 (n | xlCommand),其中 n 是大于等于 0 的小数,xlCommand 定义为 0x8000 十六进制数。

调用使用对话框的 Excel 命令

一些命令代码与 Excel 中使用对话框的操作相对应。 例如,xlcFileDelete 采用单一参数:文件名或掩码。 这可通过对话框进行调用,因此用户可取消或修改“删除”操作。 也可在不使用对话框的情况下调用它,此时删除一个或多个文件,但不进行任何后续交互,其中假定文件已存在且调用方具有相关权限。 若要在其对话框形式中调用此类命令,则必须使用按位 OR 操作和 0x1000 (xlPrompt) 来组合命令枚举。

下面的代码示例删除当前目录中与掩码my_data*.bak匹配的文件,仅当参数为 true 时才显示对话框。

bool delete_my_backup_files(bool show_dialog)
{
    XLOPER12 xResult, xFilter;
    xFilter.xltype = xltypeStr;
    xFilter.val.str = L"\014my_data*.bak"; // String length: 14 octal
    int cmd;
    if(show_dialog)
        cmd = xlcFileDelete | xlPrompt;
    else
        cmd = xlcFileDelete;
// xResult should be Boolean TRUE if successful, in which
// case return true; otherwise, false.
    return (Excel12(cmd, &xResult, 1, &xFilter) == xlretSuccess
        && xResult.xltype == xltypeBool
        && xResult.val.xbool == 1);
}

在国际版本中调用函数和命令

可配置 Excel,以按各种语言显示函数和 XLM 命令名称。 某些 C API 命令和函数对解释为函数或命令名称的字符串进行操作。 例如,xlcFormula 采用要置于指定的单元格中的字符串参数。 要使加载项适用于所有语言设置,可提供英语字符串名称并在函数或命令枚举中设置位数 0x2000 (xlIntl)。

以下代码示例将单元格 A2 中 =SUM(X1:X100) 的等效项放置在活动工作表上。 请注意,它使用框架函数 TempActiveRef 来创建临时外部引用 XLOPER。 公式将按由区域设置确定的适当语言在 A2 中显示(如果语言为法语,则为 =SOMME(X1:X100))。

int WINAPI InternationlExample(void)
{
    XLOPER12 xSum, xResult;
    xSum.xltype = xltypeStr;
    xSum.val.str = L"\015=SUM(X1:X100)";
    Excel12(xlcFormula | xlIntl, &xResult, 2,
        &xSum, TempActiveRef(2,2,1,1));
    return 1;
}

注意

由于对 Excel12 的调用的结果不是必需的,则可传递 0 (NULL) 作为第二个参数,而不是传递 xResult 的地址。 此内容将在下一部分中详细介绍。

仅 DLL 函数和命令

Excel 支持少量仅可通过 DLL 或 XLL 访问的函数。 这些函数在头文件中定义为 (n | xlSpecial),其中 n 是大于等于 0 的小数,xlSpecial 定义为 0x4000 十六进制数。 这些函数在下表中列出且记录在 API 函数引用中。

函数 n xlSpecial 说明
xlFree 0 xlSpecial 释放由 Excel 分配的内存资源。
xlStack 1 xlSpecial 返回 Excel 堆栈上的空闲空间。
xlCoerce 2 xlSpecial XLOPERXLOPER12 类型之间转换
xlSet 3 xlSpecial 提供用于设置单元格值的快捷方式。
xlSheetId 4 xlSpecial 从其内部 ID 获取工作表名称。
xlSheetNm 5 xlSpecial 从其名称中获取工作表内部 ID。
xlAbort 6 xlSpecial 验证用户是否已单击“取消”按钮或按 Esc 键。
xlGetInst 7 xlSpecial 获取 Excel 实例句柄。
xlGetHwnd 8 xlSpecial 获取 Excel 主窗口句柄。
xlGetName 9 xlSpecial 获取 DLL 的路径和文件名。
xlEnableXLMsgs 10 xlSpecial 此函数已弃用,因此无需再调用。
xlDisableXLMsgs 11 xlSpecial 此函数已弃用,因此无需再调用。
xlDefineBinaryName 12 xlSpecial 指定一个永久二进制存储名称。
xlGetBinaryName 13 xlSpecial 获取永久二进制存储名称的数据。

返回值 XLOPER/XLOPER12:operRes

operRes 参数是回调的第二个参数,并且是一个指向 XLOPERExcel4Excel4v)或 XLOPER12Excel12Excel12v)的指针。 成功调用后,它包含函数或命令的返回值。 如果无需返回值,则可将 operRes 设置为 0(NULL 指针)。 已覆盖 operRes 的先前内容,因此在调用之前必须释放之前指向的所有内存,以避免内存泄露。

如果无法调用函数或命令(例如,在参数错误的情况下),operRes 设置为错误 #VALUE!。 如果成功,命令始终返回 BooleanTRUE ;如果失败或用户取消,则始终返回 FALSE

后续参数数量:count

count 是回调的第三个参数,它是一个 32 位的带符号整数。 应将其设置为后续参数数量(从 1 开始计数)。 如果函数或命令不采用任何参数,则应设置为 0。 在 Microsoft Office Excel 2003 中,任意函数可采用的参数不得超过 30 个,但大多数函数使用更少的参数。 自 Excel 2007 起,任意函数可采用的参数数目上限增加至 255 个。

使用 Excel4Excel12 时,count 是指向正在传递的 XLOPERXLOPER12 值的指针数量。 应小心谨慎,不要传递比 count 所设值更少的参数。 否则,会导致 Excel 先于堆栈进行读取并尝试处理无效的 XLOPERXLOPER12 值,而这会导致应用程序崩溃。

使用 Excel4vExcel12v 时,count 是指向要传递为下一个且最后一个参数的 XLOPERXLOPER12 值的数组大小。 同样,应注意不要传递大小小于 count 元素的数组,因此这会导致溢出数组边界。

向 C API 函数传递参数

Excel4Excel12 均在 count 后面使用变量长度参数列表,它们被分别解释为指向 XLOPERXLOPER12 值的指针。 Excel4vExcel12vcount 后面使用单个参数,它是指向 XLOPER 值(若为 Excel4v)和指向 XLOPER12 值(若为 Excel12v)的指针数组。

借助 Excel4vExcel12v 数组形式,可在参数数目是一个变量时明确编码对 C API 的调用。 下例显示了一个函数,它采用由变量确定大小的数字数组,并通过 C API 使用 Excel 工作表函数计算总和、平均值、最大值和最小值。

void Excel12v_example(double *dbl_array, int size, double &sum, double &average, double &min, double &max)
{
// 30 is the limit in Excel 2003. 255 is the limit in Excel 2007.
// Use the lower limit to be safe, although it is better to make
// the function version-aware and use the correct limit.
    if(size < 1 || size > 30)
        return;
// Create an array of XLOPER12 values.
    XLOPER12 *xOpArray = (XLOPER12 *)malloc(size * sizeof(XLOPER12));
// Create an array of pointers to XLOPER12 values.
    LPXLOPER12 *xPtrArray =
        (LPXLOPER12 *)malloc(size * sizeof(LPXLOPER12));
// Initialize and populate the array of XLOPER12 values
// and set up the pointers in the pointer array.
    for(int i = 0; i < size; i++)
    {
        xOpArray[i].xltype = xltypeNum;
        xOpArray[i].val.num = dbl_array[i];
        xPtrArray[i] = xOpArray + i;
    }
    XLOPER12 xResult;
    int retval;
    int fn[4] = {xlfSum, xlfAverage, xlfMin, xlfMax};
    double *result_ptr[4] = {&sum, &average, &min, &max};
    for(i = 0; i < 4; i++)
    {
        retval = Excel12v(fn[i], &xResult, size, xPtrArray);
        if(retval == xlretSuccess && xResult.xltype == xltypeNum)
            *result_ptr[i] = xResult.val.num;
    }
    free(xPtrArray);
    free(xOpArray);
}

如果在先前代码中将对 XLOPER12 值的引用替换为 XLOPER,并将 Excel12v 替换为 Excel4v,则会导致出现一个适合所有 Excel 版本的函数。 Excel 函数 SUMAVERAGEMINMAX 的此操作足够简单,因此可使用 C 更高效地编码它们,同时避免因准备参数和调用 Excel 而产生的开销。 但是,Excel 包含的很多函数更加复杂,这使得此方法在某些情况下很有用。

xlfRegister 主题另外提供了一个 Excel4vExcel12v 使用示例。 注册 XLL 工作表函数时,可为“粘贴函数”对话框中的使用的每个参数应用描述性字符串。 因此,要应用于 xlfRegister 的参数总数由 XLL 函数使用的参数数量而定,且随函数而异。

如果始终调用具有相同参数数量的 C API 或命令,则需要避免额外的步骤(即,为这些参数创建指针数组)。 在这些情况下,使用 Excel4Excel12 时操作更简单、界面更清晰。 例如,注册 XLL 函数和命令时,需要提供 DLL 或 XLL 的完整路径和文件名称。 可通过对 xlfGetName 的调用获取文件名,然后通过对 xlFree 的调用释放它,如 Excel4Excel12 的下述示例所示。

XLOPER xDllName;
if(Excel4(xlfGetName, &xDllName, 0) == xlretSuccess)
{
    // Use the name, and 
    // then free the memory that Excel allocated for the string.
    Excel4(xlFree, 0, 1, &xDllName);
}
XLOPER12 xDllName;
if(Excel12(xlfGetName, &xDllName, 0) == xlretSuccess)
{
    // Use the name, and
    // then free the memory that Excel allocated for the string.
    Excel12(xlFree, 0, 1, &xDllName);
}

在实践中,可以通过创建单个 xltypeMultiXLOPER12 参数并使用 Excel12 调用 C API 来更高效地编写函数(Excel12v_example),如以下示例所示。

void Excel12_example(double *dbl_array, int size, double &sum, double &average, double &min, double &max)
{
// In this implementation, the upper limit is the largest
// single column array (equals 2^20, or 1048576, rows in Excel 2007).
    if(size < 1 || size > 1048576)
        return;
// Create an array of XLOPER12 values.
    XLOPER12 *xOpArray = (XLOPER12 *)malloc(size * sizeof(XLOPER12));
// Create and initialize an xltypeMulti array
// that represents a one-column array.
    XLOPER12 xOpMulti;
    xOpMulti.xltype = xltypeMulti;
    xOpMulti.val.array.lparray = xOpArray;
    xOpMulti.val.array.columns = 1;
    xOpMulti.val.array.rows = size;
// Initialize and populate the array of XLOPER12 values.
    for(int i = 0; i < size; i++)
    {
        xOpArray[i].xltype = xltypeNum;
        xOpArray[i].val.num = dbl_array[i];
    }
    XLOPER12 xResult;
    int fn[4] = {xlfSum, xlfAverage, xlfMin, xlfMax};
    double *result_ptr[4] = {&sum, &average, &min, &max};
    for(i = 0; i < 4; i++)
    {
        Excel12(fn[i], &xResult, 1, &xOpMulti);
        if(xResult.xltype == xltypeNum)
            *result_ptr[i] = xResult.val.num;
    }
    free(xOpArray);
}

注意

在本例中,将忽略 Excel12 的返回值。 代码改为检查所返回的 XLOPER12 是否为 xltypeNum,从而确定调用是否成功。

XLCallVer

除了回调 Excel4Excel4vExcel12Excel12v,Excel 还将导出函数 XLCallVer,它会返回 C API 当前正在运行的版本。

函数原型如下:

int pascal XLCallVer(void);

可通过任意 XLL 命令或函数调用此函数(它是线程安全的)。

在 Excel 97 至 Excel 2003 中,XLCallVer 返回 1280 = 0x0500 hex = 5 x 256,它表示 Excel 版本 5。 自 Excel 2007 起,它返回 3072 = 0x0c00 hex = 12 x 256,这表示版本 12。

虽然可使用此项来确定能否在运行时使用新的 C API,但你可能偏向于使用 Excel4(xlfGetWorkspace, &version, 1, &arg) 来检测正在运行的 Excel 版本,其中 arg 是一个设置为 2 的数值 XLOPER。 该函数返回一个字符串 XLOPER,它表示版本且随后可强制转换为整数。 依赖于 Excel 版本而非 C API 版本的原因在于,加载项同样可能需要检测的 Excel 2000、Excel 2002 和 Excel 2003 之间存在差异。 例如,部分统计函数的准确性进行了更改。

另请参阅