Excel 加载项:for 循环中的赋值导致分段错误,但逐行赋值有效。为什么?
Excel Add-in: Assignment in for loop causes segmentation fault but line-by-line assignments work. Why?
我刚刚测试了一个玩具 Excel 插件项目,使用 mingw32 工具链交叉构建 XLL。
这是我的代码:
//testXLL.c
#include "windows.h"
#include "xlcall.h"
#define MEMORYSIZE 65535000
char vMemBlock[MEMORYSIZE];
int vOffsetMemBlock =0;
LPSTR GetTempMemory(int cBytes){
LPSTR lpMemory;
if(vOffsetMemBlock + cBytes > MEMORYSIZE)
return 0;
else{
lpMemory = (LPSTR) &vMemBlock + vOffsetMemBlock;
vOffsetMemBlock += cBytes;
if(vOffsetMemBlock & 1) vOffsetMemBlock++;
return lpMemory;
}
}
LPXLOPER TempStr(LPSTR lpstr){
LPXLOPER lpx;
int chars;
lpx = (LPXLOPER)GetTempMemory(sizeof(XLOPER));
if(!lpx) return 0;
chars = lstrlen(lpstr);
if(chars>255) chars=255;
lpx->val.str=(char*)GetTempMemory((sizeof(char)*chars+1));
if(!lpx->val.str) return 0;
strncpy(lpx->val.str, lpstr,chars);
lpx->val.str[0]=(BYTE) chars;
//lpx->val.str[chars]='[=11=]';
lpx->xltype = xltypeStr;
return lpx;
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) double __stdcall myadd2(double a1,double a2){
return a1+a2;
}
static char functionTable[11][255] =
{" myadd2", // procedure
" BBB", // type_text
" add", // function_text
" add1,add2", // argument_text
" 1", // macro_type
" category", // category
" ", // shortcut_text
" some help topic", // help_topic
" Adds toy", // function_help
" 1st.", // argument_help1
" 2nd" // argument_help2
};
__declspec(dllexport) int __stdcall xlAutoOpen(){
LPXLOPER pxDLL;
Excel4(xlGetName,pxDLL,0);
XLOPER xlRegArgs[11];
for(int i = 0; i < 11; i++){
xlRegArgs[i] = *TempStr(functionTable[i]);
}
Excel4(xlfRegister, 0, 12,
pxDLL,
&xlRegArgs[0], &xlRegArgs[1], &xlRegArgs[2],
&xlRegArgs[3], &xlRegArgs[4], &xlRegArgs[5],
&xlRegArgs[6], &xlRegArgs[7], &xlRegArgs[8],
&xlRegArgs[9], &xlRegArgs[10]);
return 1;
}
__declspec(dllexport) LPXLOPER __stdcall xlAddInManagerInfo(LPXLOPER xlAction) {
static XLOPER xlReturn, xlLongName, xlTemp;
xlTemp.xltype = xltypeInt;
xlTemp.val.w = xltypeInt;
Excel4(xlCoerce, &xlReturn, 2, xlAction, &xlTemp);
if(1 == xlReturn.val.w) {
xlLongName = *TempStr(" xll-name");
} else {
xlLongName.xltype = xltypeErr;
xlLongName.val.err = xlerrValue;
}
return &xlLongName;
}
#ifdef __cplusplus
}
#endif
我在 Ubuntu 中构建了这个 testXLL.c 文件:
>i686-w64-mingw32-gcc -shared -Wl,--kill-at testXLL.c -o win.xll -L. -lxlcall32
这会成功生成“win.xll”,但是在加载此 win.xll 时,Excel 会崩溃。
在Windows10,我尝试使用gdb调试它,但我无法捕捉到xll文件中的断点——它在加载时自动被禁用。但是我可以在 gdb 输出中看到,Excel 崩溃时出现分段错误。
XLOPER xlRegArgs[11];
for(int i = 0; i < 11; i++){
xlRegArgs[i] = *TempStr(functionTable[i]);
}
奇怪的是,如果我用 xlAutoOpen
函数中的以下逐行赋值替换上面的 for
循环,编译后的 XLL 文件在 Excel:
XLOPER xlRegArgs[11];
xlRegArgs[0] = *TempStr(functionTable[0]);
xlRegArgs[1] = *TempStr(functionTable[1]);
xlRegArgs[2] = *TempStr(functionTable[2]);
xlRegArgs[3] = *TempStr(functionTable[3]);
xlRegArgs[4] = *TempStr(functionTable[4]);
xlRegArgs[5] = *TempStr(functionTable[5]);
xlRegArgs[6] = *TempStr(functionTable[6]);
xlRegArgs[7] = *TempStr(functionTable[7]);
xlRegArgs[8] = *TempStr(functionTable[8]);
xlRegArgs[9] = *TempStr(functionTable[9]);
xlRegArgs[10] = *TempStr(functionTable[10]);
请赐教。这两种分配方法有什么区别?
虽然我(还)没有完整解释这种行为,但我post认为这是可能的'workaround',我在我的一个项目中遇到的一个非常相似的案例中使用了它。
问题似乎是某种形式的'stack corruption',是由于使用函数局部变量(i
)作为循环索引引起的;将其转换为 global/static 变量可能会解决此问题。以下代码片段是建议的修复(我更改了索引变量的名称以避免代码中其他地方可能发生名称冲突):
///...
static int regloop; // Used as the loop index, below...
__declspec(dllexport) int __stdcall xlAutoOpen(){
LPXLOPER pxDLL;
Excel4(xlGetName,pxDLL,0);
XLOPER xlRegArgs[11];
for(regloop = 0; regloop < 11; regloop++){
xlRegArgs[regloop] = *TempStr(functionTable[regloop]);
}
这是我上述项目中的代码部分(但请注意这是 C++/MFC)表现出相同类型的行为 – 但 仅 在 x86 构建中(x64 构建工作没有问题):
static int plin; // NOTA BENE:- We use this in the two functions below, as the use of
// a local 'plin' loop index is prone to induce stack corruption (?),
// especially in MSVC 2017 (MFC 14) builds for x86.
void BasicApp::OnUpdatePICmd(uint32_t nID, void *pUI)
{
//! for (int plin = 0; plin < Plugin_Number; ++plin) { // Can cause problems - vide supra
for (plin = 0; plin < Plugin_Number; ++plin) {
BOOL mEbl = FALSE; int mChk = -1;
if ((Plugin_UDCfnc[plin] != nullptr) && Plugin_UDCfnc[plin](nID, &mEbl, &mChk)) {
CommandEnable(pUI, mEbl ? true : false);
if (mChk >= 0) CmdUISetCheck(pUI, mChk);
return;
}
}
CommandEnable(pUI, false);
return;
}
(Plugin_UDCfnc
是 BasicApp
class 的 static
数组成员。)
在编写上述代码后的这些年里,我偶尔 'fleeting insights' 了解为什么会发生这种情况,但截至目前,我无法提供更可靠的修复。如果我偶然发现了解决方案,我将重新审视这个问题并更新此 post。同时,欢迎其他人将此作为'clue'和post自己的explanations/solutions。
我刚刚测试了一个玩具 Excel 插件项目,使用 mingw32 工具链交叉构建 XLL。
这是我的代码:
//testXLL.c
#include "windows.h"
#include "xlcall.h"
#define MEMORYSIZE 65535000
char vMemBlock[MEMORYSIZE];
int vOffsetMemBlock =0;
LPSTR GetTempMemory(int cBytes){
LPSTR lpMemory;
if(vOffsetMemBlock + cBytes > MEMORYSIZE)
return 0;
else{
lpMemory = (LPSTR) &vMemBlock + vOffsetMemBlock;
vOffsetMemBlock += cBytes;
if(vOffsetMemBlock & 1) vOffsetMemBlock++;
return lpMemory;
}
}
LPXLOPER TempStr(LPSTR lpstr){
LPXLOPER lpx;
int chars;
lpx = (LPXLOPER)GetTempMemory(sizeof(XLOPER));
if(!lpx) return 0;
chars = lstrlen(lpstr);
if(chars>255) chars=255;
lpx->val.str=(char*)GetTempMemory((sizeof(char)*chars+1));
if(!lpx->val.str) return 0;
strncpy(lpx->val.str, lpstr,chars);
lpx->val.str[0]=(BYTE) chars;
//lpx->val.str[chars]='[=11=]';
lpx->xltype = xltypeStr;
return lpx;
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) double __stdcall myadd2(double a1,double a2){
return a1+a2;
}
static char functionTable[11][255] =
{" myadd2", // procedure
" BBB", // type_text
" add", // function_text
" add1,add2", // argument_text
" 1", // macro_type
" category", // category
" ", // shortcut_text
" some help topic", // help_topic
" Adds toy", // function_help
" 1st.", // argument_help1
" 2nd" // argument_help2
};
__declspec(dllexport) int __stdcall xlAutoOpen(){
LPXLOPER pxDLL;
Excel4(xlGetName,pxDLL,0);
XLOPER xlRegArgs[11];
for(int i = 0; i < 11; i++){
xlRegArgs[i] = *TempStr(functionTable[i]);
}
Excel4(xlfRegister, 0, 12,
pxDLL,
&xlRegArgs[0], &xlRegArgs[1], &xlRegArgs[2],
&xlRegArgs[3], &xlRegArgs[4], &xlRegArgs[5],
&xlRegArgs[6], &xlRegArgs[7], &xlRegArgs[8],
&xlRegArgs[9], &xlRegArgs[10]);
return 1;
}
__declspec(dllexport) LPXLOPER __stdcall xlAddInManagerInfo(LPXLOPER xlAction) {
static XLOPER xlReturn, xlLongName, xlTemp;
xlTemp.xltype = xltypeInt;
xlTemp.val.w = xltypeInt;
Excel4(xlCoerce, &xlReturn, 2, xlAction, &xlTemp);
if(1 == xlReturn.val.w) {
xlLongName = *TempStr(" xll-name");
} else {
xlLongName.xltype = xltypeErr;
xlLongName.val.err = xlerrValue;
}
return &xlLongName;
}
#ifdef __cplusplus
}
#endif
我在 Ubuntu 中构建了这个 testXLL.c 文件:
>i686-w64-mingw32-gcc -shared -Wl,--kill-at testXLL.c -o win.xll -L. -lxlcall32
这会成功生成“win.xll”,但是在加载此 win.xll 时,Excel 会崩溃。
在Windows10,我尝试使用gdb调试它,但我无法捕捉到xll文件中的断点——它在加载时自动被禁用。但是我可以在 gdb 输出中看到,Excel 崩溃时出现分段错误。
XLOPER xlRegArgs[11];
for(int i = 0; i < 11; i++){
xlRegArgs[i] = *TempStr(functionTable[i]);
}
奇怪的是,如果我用 xlAutoOpen
函数中的以下逐行赋值替换上面的 for
循环,编译后的 XLL 文件在 Excel:
XLOPER xlRegArgs[11];
xlRegArgs[0] = *TempStr(functionTable[0]);
xlRegArgs[1] = *TempStr(functionTable[1]);
xlRegArgs[2] = *TempStr(functionTable[2]);
xlRegArgs[3] = *TempStr(functionTable[3]);
xlRegArgs[4] = *TempStr(functionTable[4]);
xlRegArgs[5] = *TempStr(functionTable[5]);
xlRegArgs[6] = *TempStr(functionTable[6]);
xlRegArgs[7] = *TempStr(functionTable[7]);
xlRegArgs[8] = *TempStr(functionTable[8]);
xlRegArgs[9] = *TempStr(functionTable[9]);
xlRegArgs[10] = *TempStr(functionTable[10]);
请赐教。这两种分配方法有什么区别?
虽然我(还)没有完整解释这种行为,但我post认为这是可能的'workaround',我在我的一个项目中遇到的一个非常相似的案例中使用了它。
问题似乎是某种形式的'stack corruption',是由于使用函数局部变量(i
)作为循环索引引起的;将其转换为 global/static 变量可能会解决此问题。以下代码片段是建议的修复(我更改了索引变量的名称以避免代码中其他地方可能发生名称冲突):
///...
static int regloop; // Used as the loop index, below...
__declspec(dllexport) int __stdcall xlAutoOpen(){
LPXLOPER pxDLL;
Excel4(xlGetName,pxDLL,0);
XLOPER xlRegArgs[11];
for(regloop = 0; regloop < 11; regloop++){
xlRegArgs[regloop] = *TempStr(functionTable[regloop]);
}
这是我上述项目中的代码部分(但请注意这是 C++/MFC)表现出相同类型的行为 – 但 仅 在 x86 构建中(x64 构建工作没有问题):
static int plin; // NOTA BENE:- We use this in the two functions below, as the use of
// a local 'plin' loop index is prone to induce stack corruption (?),
// especially in MSVC 2017 (MFC 14) builds for x86.
void BasicApp::OnUpdatePICmd(uint32_t nID, void *pUI)
{
//! for (int plin = 0; plin < Plugin_Number; ++plin) { // Can cause problems - vide supra
for (plin = 0; plin < Plugin_Number; ++plin) {
BOOL mEbl = FALSE; int mChk = -1;
if ((Plugin_UDCfnc[plin] != nullptr) && Plugin_UDCfnc[plin](nID, &mEbl, &mChk)) {
CommandEnable(pUI, mEbl ? true : false);
if (mChk >= 0) CmdUISetCheck(pUI, mChk);
return;
}
}
CommandEnable(pUI, false);
return;
}
(Plugin_UDCfnc
是 BasicApp
class 的 static
数组成员。)
在编写上述代码后的这些年里,我偶尔 'fleeting insights' 了解为什么会发生这种情况,但截至目前,我无法提供更可靠的修复。如果我偶然发现了解决方案,我将重新审视这个问题并更新此 post。同时,欢迎其他人将此作为'clue'和post自己的explanations/solutions。