c-Excel外接程序:循环中的赋值导致分段错误,但逐行赋值有效.为什么



我刚刚测试了一个玩具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]='';
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]);

请启发我。这两种作业方法有什么区别?

虽然我(还(没有对这种行为的完整解释,但我将其作为一种可能的"变通方法"发布,我在一个项目中遇到过类似的情况。

问题似乎是由使用用作循环索引的函数局部变量(i(引起的某种形式的"堆栈损坏";将其转换为全局/静态变量可能会解决此问题。以下代码片段是一个建议的修复程序(我已经更改了索引变量的名称,以避免代码中其他地方可能的名称冲突(:

///...
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_UDCfncBasicApp类的static阵列成员。(

在编写上述代码后的几年里,我偶尔会对为什么会发生这种情况有"稍纵即逝的见解",但到目前为止,我还无法提供更可靠的解决方案。如果我偶然发现一个解决方案,我将重新审视这个问题并更新这篇帖子。同时,欢迎其他人将此作为"线索",并发布自己的解释/解决方案。

最新更新