从 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 可通过回调函数 Excel4、Excel4v、Excel12 和 Excel12v 访问命令和函数。
Excel 版本 4 中引入了 Excel4 和 Excel4v 函数。 它们适用于 XLOPER 数据结构。 Excel 2007 引入了两个新的回调函数 Excel12 和 Excel12v,它们适用于 XLOPER12 数据结构。 Excel4 和 Excel4v 函数由 Xlcall32.lib 库导出,后者必须包含在 DLL 或 XLL 项目中。 Excel12 和 Excel12v 包含在 SDK C++ 源文件 Xlcall.cpp 中;如果想要通过 XLOPER12 结构访问 Excel 功能,则必须在项目中包含此文件。
以下代码显示了上述 4 个函数的函数原型。 前三个参数相同,只是第二个参数是一个指针,它同时指向第一对中的 XLOPER 和第二对中的 XLOPER12。 Excel4 和 Excel12 中的调用约定是 _cdecl,它允许变量参数列表。 省略号表示指向 Excel4 的 XLOPER 值和 Excel12 的 XLOPER12 值的指针。 指针数量等于 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 能够调用 Excel4、Excel4v、Excel12 或 Excel12v,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 | 一个或多个参数 XLOPER 或 XLOPER12 值构建或填充错误。 |
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),则 XLOPER 或 XLOPER12 返回值也将设置为 #VALUE!。 在某些情况下,检查此项可能足以测试是否成功,但应注意,调用可同时返回 xlretSuccess 和 #VALUE!。
如果对 C API 的调用导致 xlretUncalced 或 xlretAbort,则 DLL 或 XLL 代码应将控制权归还给 Excel,然后再进行任何其他 C API 调用(而不是调用 xlfree 函数来释放 XLOPER 和 XLOPER12 值中由 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 | 在 XLOPER 和 XLOPER12 类型之间转换 |
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 参数是回调的第二个参数,并且是一个指向 XLOPER(Excel4 和 Excel4v)或 XLOPER12(Excel12 和 Excel12v)的指针。 成功调用后,它包含函数或命令的返回值。 如果无需返回值,则可将 operRes 设置为 0(NULL 指针)。 已覆盖 operRes 的先前内容,因此在调用之前必须释放之前指向的所有内存,以避免内存泄露。
如果无法调用函数或命令(例如,在参数错误的情况下),operRes 设置为错误 #VALUE!。 如果成功,命令始终返回 BooleanTRUE ;如果失败或用户取消,则始终返回 FALSE 。
后续参数数量:count
count 是回调的第三个参数,它是一个 32 位的带符号整数。 应将其设置为后续参数数量(从 1 开始计数)。 如果函数或命令不采用任何参数,则应设置为 0。 在 Microsoft Office Excel 2003 中,任意函数可采用的参数不得超过 30 个,但大多数函数使用更少的参数。 自 Excel 2007 起,任意函数可采用的参数数目上限增加至 255 个。
使用 Excel4 和 Excel12 时,count 是指向正在传递的 XLOPER 或 XLOPER12 值的指针数量。 应小心谨慎,不要传递比 count 所设值更少的参数。 否则,会导致 Excel 先于堆栈进行读取并尝试处理无效的 XLOPER 或 XLOPER12 值,而这会导致应用程序崩溃。
使用 Excel4v 和 Excel12v 时,count 是指向要传递为下一个且最后一个参数的 XLOPER 或 XLOPER12 值的数组大小。 同样,应注意不要传递大小小于 count 元素的数组,因此这会导致溢出数组边界。
向 C API 函数传递参数
Excel4 和 Excel12 均在 count 后面使用变量长度参数列表,它们被分别解释为指向 XLOPER 和 XLOPER12 值的指针。 Excel4v 和 Excel12v 在 count 后面使用单个参数,它是指向 XLOPER 值(若为 Excel4v)和指向 XLOPER12 值(若为 Excel12v)的指针数组。
借助 Excel4v 和 Excel12v 数组形式,可在参数数目是一个变量时明确编码对 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 函数 SUM、AVERAGE、MIN 和 MAX 的此操作足够简单,因此可使用 C 更高效地编码它们,同时避免因准备参数和调用 Excel 而产生的开销。 但是,Excel 包含的很多函数更加复杂,这使得此方法在某些情况下很有用。
xlfRegister 主题另外提供了一个 Excel4v 和 Excel12v 使用示例。 注册 XLL 工作表函数时,可为“粘贴函数”对话框中的使用的每个参数应用描述性字符串。 因此,要应用于 xlfRegister 的参数总数由 XLL 函数使用的参数数量而定,且随函数而异。
如果始终调用具有相同参数数量的 C API 或命令,则需要避免额外的步骤(即,为这些参数创建指针数组)。 在这些情况下,使用 Excel4 和 Excel12 时操作更简单、界面更清晰。 例如,注册 XLL 函数和命令时,需要提供 DLL 或 XLL 的完整路径和文件名称。 可通过对 xlfGetName 的调用获取文件名,然后通过对 xlFree 的调用释放它,如 Excel4 和 Excel12 的下述示例所示。
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
除了回调 Excel4、Excel4v、Excel12 和 Excel12v,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 之间存在差异。 例如,部分统计函数的准确性进行了更改。