On Monday, 20 March 2017 at 20:09:58 UTC, Atila Neves wrote:
http://code.dlang.org/packages/excel-d

This dub package allows D code to be called from Excel. It uses compile-time reflection to register the user's code in an XLL (a DLL loaded by Excel) so no boilerplate is necessary. Not even `DllMain`! It works like this:

main.d:

import xlld;
mixin(wrapAll!(__MODULE__, "funcs"));

funcs.d:

import xlld;

@Register(ArgumentText("Array to add"),
          HelpTopic("Adds all cells in an array"),
          FunctionHelp("Adds all cells in an array"),
          ArgumentHelp(["The array to add"]))
double FuncAddEverything(double[][] args) nothrow @nogc {
    import std.algorithm: fold;
    import std.math: isNaN;

    double ret = 0;
    foreach(row; args)
        ret += row.fold!((a, b) => b.isNaN ? 0.0 : a + b)(0.0);
    return ret;
}

This code, once compiled to an XLL (see the example in the repository) and loaded in Excel, will permit a user to write `=FuncAddEverything(B1:D6)` and have the cell populated with the sum of all arguments in that range.

There's a lot going on behind the scenes, and that's the point. For instance, the function above takes a 2d array of doubles and returns a double, but those aren't Excel types. The wrapper code writes out an Excel-compatible type signature at compile-time, does all the conversions, calls the user's code then converts back to types Excel can understand.

The user functions have to be `nothrow`. This is guaranteed at compile-time and the user gets a warning message about the function not being considered. `@nogc` is optional but won't work if returning a string due to allocations. The code is compatible with std.experimental.allocator internally but there's no way to specify an allocator currently for the registration.

I can make the registration mixin easier to use but haven't gotten around to it yet. I thought it was better to put the code out there as is than wait.

This is another one of those "only in D" packages due to the metaprogramming, which is always nice.

Atila

Now with more `@nogc`. Before, this worked fine:

double func(double d) @nogc nothrow { return d * 2; }

The function is `@nogc`, the wrapper function (i.e. the function that Excel actually calls) is also `@nogc` via the magic of compile-time reflection. So far, so good. But what if you want to return a string or an array back to Excel. Oh, oh...

Enter the `@Dispose` UDA:


// @Dispose is used to tell the framework how to free memory that is dynamically // allocated by the D function. After returning, the value is converted to an // Excel type sand the D value is freed using the lambda defined here.
@Dispose!((ret) {
    import std.experimental.allocator.mallocator: Mallocator;
    import std.experimental.allocator: dispose;
    Mallocator.instance.dispose(ret);
})
double[] FuncReturnArrayNoGc(double[] numbers) @nogc @safe nothrow {
    import std.experimental.allocator.mallocator: Mallocator;
    import std.experimental.allocator: makeArray;
    import std.algorithm: map;

    try {
// Allocate memory here in order to return an array of doubles.
        // The memory will be freed after the call by calling the
        // function in `@Dispose` above
return Mallocator.instance.makeArray(numbers.map!(a => a * 2));
    } catch(Exception _) {
        return [];
    }
}

And Bob's your uncle.

Atila



Reply via email to