65.9K
CodeProject 正在变化。 阅读更多。
Home

Excel中的自定义函数:第四部分 - XLLs

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2012年7月13日

CPOL

3分钟阅读

viewsIcon

18139

XLLs 速度显著更快,并允许开发人员定义函数的名称/参数。

引言

在之前的文章中,我们已经了解了 VBA、自动化加载项和 RTDs。 XLLs 速度显著更快,并允许开发人员定义函数的名称/参数。 它们实现起来也不难,尽管设置函数的定义可能有点棘手。 与其他 C++ 加载项不同,XLL 不是 COM 服务器,它使用 Excel C API。

必备组件

您需要 Excel 2010 XLL SDK,如果您有 Excel 2007,则使用“以前的版本”。

入门

XLL 只是一个 DLL,它导出一组 Excel 将调用的标准函数。 最初,我打算从头开始为这篇文章创建一些东西,但 SDK 附带的示例已经可以工作,因此我将重点介绍可以增强它的几种方法。 我们将改进注册函数的过程,以便代码更易于编写,并且更易于记录函数/参数,以便用户知道如何使用它。

框架

在使用示例之前,我们需要构建 samples 目录中包含的 Framework 项目。 打开“Excel2010XLLSDK\SAMPLES\FRAMWRK\Framework.sln”。 如果需要转换,转换后直接构建即可。

XLL 示例

打开示例“Excel2010XLLSDK\SAMPLES\EXAMPLE\Example.sln”,如果您使用的是较新版本的 Visual Studio,它将自动转换它。 如果你打开 example.c,你会注意到缺少一些包含文件。

1_underlines

图 1:缺少几个包含文件。

要修复此问题,只需在解决方案资源管理器中单击“Example”项目,然后在“属性页”中,转到“配置属性”和“VC++ 目录”,然后添加 *include* 目录和 *library* 目录。 将“<BASE_DIR>\Excel2010XLLSDK\INCLUDE”和“<BASE_DIR>\Excel2010XLLSDK\SAMPLES\FRAMWRK”添加到 *Include* 目录。 将“<BASE_DIR>\Excel2010XLLSDK\LIB”和“<BASE_DIR>\Excel2010XLLSDK\SAMPLES\FRAMWRK\Debug”添加到 *Library* 目录。 在我的例子中,我用“C:\2010 Office System Developer Resource”替换了 BASE_DIR,但您的可能不同。

2_property_pages

图 2:Example 项目的属性页。

现在您应该可以构建该项目了。

简单的函数注册

现在,函数的注册看起来像这样

static LPWSTR rgFuncs[rgFuncsRows][7] = {
    {L"CallerExample",		L"I", L"CallerExample"},
    {L"debugPrintfExample",	L"I", L"debugPrintfExample"},
    {L"EvaluateExample",		L"I", L"EvaluateExample"},
    {L"Excel12fExample",		L"I", L"Excel12fExample"},
    {L"Excel12Example",		L"I", L"Excel12Example"},

// Then later on...

        for (i=0;i < rgFuncsRows;i++) 
    {
        Excel12f(xlfRegister, 0, 4,
            (LPXLOPER12)&xDLL,
            (LPXLOPER12)TempStr12(rgFuncs[i][0]),
            (LPXLOPER12)TempStr12(rgFuncs[i][1]),
            (LPXLOPER12)TempStr12(rgFuncs[i][2]));
    }
图 2:示例中函数的注册方式。

借助辅助函数,我们将能够注册具有可变数量参数的函数,并且我们可以定义与参数关联的帮助,这些帮助将显示在函数向导中。 XLLRegisterInfo 结构也可以动态定义,因此我们可以使用它来为描述/帮助提供本地化的 string

VOID RegisterHelper(struct XLLRegisterInfo registryInfo)
{
    static XLOPER12 xDLL, xRegId;
    static LPXLOPER12 args[260];
    int count;
    int i;    
    
    count = 10 + registryInfo.argumentCount;

    /*
        Get the name of the DLL.
    */
    Excel12f(xlGetName, &xDLL, 0);    

    if(registryInfo.argumentCount == 0)
    {
        Excel12f(xlfRegister, 0, count,
            (LPXLOPER12)&xDLL, /* moduleText */
            (LPXLOPER12)TempStr12(registryInfo.procedure),
            (LPXLOPER12)TempStr12(registryInfo.typeText),
            (LPXLOPER12)TempStr12(registryInfo.functionText),
            (LPXLOPER12)TempStr12(registryInfo.argumentText),
            (LPXLOPER12)TempInt12
            (registryInfo.macroType), /* function listed in function wizard */
            (LPXLOPER12)TempStr12(registryInfo.categoryName),
            (LPXLOPER12)TempStr12(L""), /* shortcut text */
            (LPXLOPER12)TempStr12(registryInfo.helpTopic),
            (LPXLOPER12)TempStr12(registryInfo.functionHelp));
    }
    else
    {
        count = 0;
        args[count++] = (LPXLOPER12)&xDLL; /* moduleText */
        args[count++] = (LPXLOPER12)TempStr12(registryInfo.procedure);
        args[count++] =    (LPXLOPER12)TempStr12(registryInfo.typeText);
        args[count++] = (LPXLOPER12)TempStr12(registryInfo.functionText);
        args[count++] = (LPXLOPER12)TempStr12(registryInfo.argumentText);
        args[count++] =    
        (LPXLOPER12)TempInt12(registryInfo.macroType); /* function listed in function wizard */
        args[count++] = (LPXLOPER12)TempStr12(registryInfo.categoryName);
        args[count++] =    (LPXLOPER12)TempStr12(L""); /* shortcut text */
        args[count++] =    (LPXLOPER12)TempStr12(registryInfo.helpTopic);
        args[count++] = (LPXLOPER12)TempStr12(registryInfo.functionHelp);
        
        for(i = 0; i < registryInfo.argumentCount; i++)
        {
            args[count++] = (LPXLOPER12)TempStr12(registryInfo.argumentHelp[i]);
        }
 
        Excel12v(xlfRegister, 0, count,
            args);
    }
}
图 3:可以调用 RegisterHelper 来设置函数,而不是允许参数帮助。

当然,您可能想要 XLLRegisterInfo 的定义,所以这是它

struct XLLRegisterInfo
{
    /**
        Name of the procedure to be registered.
    */
    LPWSTR procedure;
    /** 
        The type text see pxTypeText here: http://msdn.microsoft.com/en-us/library/bb687900.aspx
    */
    LPWSTR typeText;
    /**
        Name of the function in Excel.
    */
    LPWSTR functionText;
    /**
        String of Letters that represent arguments/types. See pxArgumentText
        here: http://msdn.microsoft.com/en-us/library/bb687900.aspx
    */
    LPWSTR argumentText;
    /**
        Type of function. 0 - Macro sheet equivalent, 1 - Worksheet Functions, 2 - Commands.
        When in doubt use 1;
     */
    int macroType;

    /**
        Name of the category to add the function to.
    */
    LPWSTR categoryName;

    /**
        Commands only.
    */
    LPWSTR shortcutText;

    /**
        Reference to a help file (.chm or .hlp). 
        Use form: filepath!HelpContextID or http://address/path_to_file_in_site!0
    */
    LPWSTR helpTopic;
    /**
        String that describes the function in the function wizard.
    */
    LPWSTR functionHelp;

    /**
      * Number of descriptions in argumentHelp.
      */
    int argumentCount;

    /**
        Array of Argument help strings. 
    */
    LPWSTR argumentHelp[15];
};
图 4:XLLRegisterInfo 结构的定义。

在 2007 中,argumentHelp 字段可以是最多 245 个参数,在 2003 中是 20 个。 为您的加载项选择合适的东西。

定义完之后,可以用这些结构的数组替换 rgFuncs

struct XLLRegisterInfo rgFuncs[] = 
{ 
    {
        L"CalcCircum" /* procedure */, L"BB" /*typeText*/, 
        /* functionText */ L"CalcCircum", 
        /*argumentText*/ L"Radius", 1, L"Examples", 
        L"" /*shortcutText*/, L"" /*helpTopic*/, 
        L"Calculates the circumferance of a circle." 
        /* functionHelp */, 1 /*argCount */,
        {L"Radius of the circle. " 
        /*argumentHelp1 pad with space, Excel bug*/}
    },
    { NULL }
};
图 5:UDF 定义数组。

我在末尾添加了 NULL 来标记数组的结尾。 这样,我不需要一个 rgFuncsCount 常量,每次添加/删除函数时我都必须更新它。 还要记得在最后一个 argumentHelp 中添加一个空格。 原因是 Excel 中存在一个错误,会删除最后一个 argumentHelp 的最后一个字符。 不要指望这个错误会被修复,但以防万一我使用空格,它看起来比笑脸更专业。

for(i=0;rgFuncs[i].procedure != NULL;i++)
    RegisterHelper(rgFuncs[i]);
图 6:在 xlAutoOpen 中,您只需要这个来注册您的函数。

现在,我可以定义具有多个参数的函数,并为每个参数提供适当的帮助,并且所有额外的复杂性都隐藏在 RegisterHelper 中。 xlAutoOpen 的代码现在也更具可读性,并且它可以处理具有不同数量参数的不同函数。

© . All rights reserved.