Excel 插件:for 循环中的赋值会导致分段错误,但逐行赋值有效为什么?

问题描述

我刚刚测试了一个玩具 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]='\0';
    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,12,&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 崩溃了。

在 Windows 10 中,我尝试使用 gdb 来调试它,但我无法在 xll 文件中捕获断点 - 它在加载时自动禁用。但是我在gdb的输出中可以看到,这是Excel崩溃时的分段错误

 XLOPER xlRegArgs[11];
for(int i = 0; i < 11; i++){
    xlRegArgs[i] = *TempStr(functionTable[i]);
}

奇怪的是,如果我将上面的 for 循环替换为 xlAutoOpen 函数中的以下逐行赋值,则编译后的 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_UDCfncstatic 类的 BasicApp 数组成员。)

自从编写上述代码以来的几年里,我偶尔对为什么会发生这种情况有“短暂的见解”,但截至目前,我无法提供更强大的修复。如果我偶然发现了一个解决方案,我将重新审视这个问题并更新这篇文章。同时,欢迎其他人以此为“线索”,发表自己的解释/解决方案。