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_UDCfncBasicApp class 的 static 数组成员。)

在编写上述代码后的这些年里,我偶尔 'fleeting insights' 了解为什么会发生这种情况,但截至目前,我无法提供更可靠的修复。如果我偶然发现了解决方案,我将重新审视这个问题并更新此 post。同时,欢迎其他人将此作为'clue'和post自己的explanations/solutions。