Giter VIP home page Giter VIP logo

xladd-derive's Introduction

xladd-derive

Macros to help write Excel User defined functions easily in Rust

Version 0.7.0 release notes

Fixed crash bug async function support and added support for single threaded functions

Version 0.6.3 release notes

Populate f64::NAN when range has #NA or #DIV/0. Might break code that assumes 0.0

Version 0.6.2 release notes

xladd would return a null value if a xlTypeMulti was returned from a range cell.

Version 0.6.1 release notes

Some minor bug fixes in xladd integrated in

Version 0.6 release notes

Update to use ndarray 0.14 Support for sref which allows use of index and offset ranges in excel Some minor bug fixes

Version 0.5 release notes

  • The main new feature is that arrays can now be 1078,576 rows in length which is the maximum that excel supports
  • To support that profiled the code as much as possible to reduce copying of arrays around. I'm sure there can be further improvements
  • Async UDFs are now supported. They are supported ok in Excel but it's not a great idea to have too many of them as they screw up Excel's dependency management. However functions are marked as threadsafe, and should be in Rust (no globals) so you should be ok. Watch out for overwrites though
  • make_row and make_col variant arrays are supported now to help return tables.
  • RTD and Ribbon Bar support coming soon

Version 0.4 release notes

New features

  • Previously, when calling a function from excel, if the user made a mistake with paramter entry it would return a generic "error". Now we get "missing parameter [name] for function [xxx]" which is a better use experience.
  • It also traps where a particular type was expected and it's not parseable.
  • trace logging added. If logging is enabled and is set to LevelFilter::Trace then you can get a log of every function call, parameters passed and resulting values. This does have a small impact on performance that I've measured even when disabled. I would recommend having a Excel UDF created that you could call such as enable_trace_logging that outputs to a file that is called on demand in your testing spreadsheet
  • Nan values are converted to #N/A in excel. This I found useful when user inputs were out of bounds and produced NAN values in some mathematical functions. Rather than crashing or skipping we get a #NA in excel telling us that we need to look at the inputs.
  • Dependency on log::* crate added

Version 0.3 release notes

New features

  • Main new feature is the ability to have 2d arrays going in and out through using NDArray.
  • Added a feature flag "use_ndarray". But I cannot see how to pass it through to xladd automatically if the old version of the crate didn't have it as a feature? PR welcome This allows you to use Array2 or Array2 types as input or output parameters. This fixes the problem of 2d arrays which was a hacky solution at best before Using &[f64] is still supported as before and still makes sense for single column or row data Using (Vec,usize) as a return type is still supported but I think it's ugly as it doesn't really show the intention of the developer

Bugfixes

  • If you specify an array (vec or array2) and you are dragging a range of values, the first cell is actually sent as a single f64 and not a range. I didn't handle this case before and could lead to a crash

Version 0.2 release notes

  • Added a new feature: prefix which can be used to name your functions. Previously all the excel exported functions were called "xl_myfunction", now with prefix = "project_" your exports are renamed to project_myfunction. If it's not specified it defaults to "xl_".
  • Added rename which renames the Excel exposed function to whatever specified. The prefix still stands.

Why?

I find quickly knocking up a sample GUI in Excel by drag/dropping extremely good for prototyping. Writing a test function and being able to pass all sorts of data to it interactively is a useful feature

Background

I found xladd from MarcusRainbow which enabled a raw api to create user defined functions for Excel. I wasn't totally happy with it and raised a couple of PRs against his project but didn't get any responses so I forked his project and contined to add functionality as I saw fit.

But it was still a pain and I wanted to learn about proc-macros so created this proc-macro crate to make it easier to write functions in Rust.

Usage

Add

[lib]
crate-type = ["cdylib"]

[dependencies]
xladd-derive= {"^0.4" }
xladd = {git="https://github.com/ronniec95/xladd" , features=["use_ndarray"] } # Needed to patch the old abandoned crate

to your Cargo.toml

Write a Rust function and add the following annotation #[xl_func()] like

// These imports are needed from xladd
use xladd::registrator::Reg;
use xladd::variant::Variant;
use xladd::xlcall::LPXLOPER12;
use xladd_derive::xl_func;
use log::*; // Needed from 0.4.* onwards to give tracing

#[xl_func()]
fn add(arg1: f64, arg2: f64) -> Result<f64, Box<dyn std::error::Error>> {
    // No comments in this method, defaults will be allocated
    Ok(arg1 + arg2)
}

/// This function adds any number of values together
/// * v - array of f64
/// * ret - returns the sum0
#[xl_func()]
fn add_array(v: &[f64]) -> Result<f64, Box<dyn std::error::Error>> {
    // Comments will be filled into the Excel function dialog box
    Ok(v.iter().sum())
}

/// This function adds any number of values together
/// * v - array of f64
/// * ret - returns the sum
#[xl_func(category="MyCategory")] // Custom attribute to assign function to a particular category
fn add_array_v2(v: &[f64]) -> Result<(Vec<f64>, usize), Box<dyn std::error::Error>> {
    // Comments will be filled into the Excel function dialog box
    // This returns a 2d array to excel using a (vec,usize) tuple. Note that if v.len() / columns != 0 you will have missing values
    Ok((v.to_vec(), 2))
}

use ndarray::Array2;
/// 2d arrays can now be accepted and returned opening up a lot more possibilities for interfacing with Excel
#[xl_func(category = "OptionPricing", prefix = "my", rename = "baz")]
fn add_f64_2(a: Array2<f64>) -> Result<Array2<f64>, Box<dyn std::error::Error>> {
    Ok(Array2::from_elem([2, 2], 0.0f64))
}

Right now there are a couple of restrictions which I hope to remove down the line

The return type of your function can be a Result<type,Box<dyn std::error::Error>> of any basic type:

  • f32
  • f64
  • i32
  • i64
  • bool
  • String (owned)

or a tuple of

  • (Vec<[basic type]>,usize)

where the second parameter is the number of columns. This allows Excel to handle arrays of 2d data. The macro will calculate the rows from the size of the array.

I was thinking of making the input &[] arrays also be a tuple if there is demand for it.

Arguments are taken as LPXLOPER12 args which are then coerced to the Rust types. Errors in coercion are reported via a trace!() log. If you run Excel from the command line with env-logger or simplelog you could output these to a file for debugging.

Documentation

The doc comments are interpreted in the following manner

/// This is a normal multiline comment that 
/// will be used as a description of the function
/// * arg1 - This argument must be formatted with a `* <name> -` and will be used in the argument description
/// * ret - This is a special return type argument which will appended to the description of the function

Multithreading

Excel uses however many cores there are on the machine, but it relies on your UDFs being thread safe. Rust is multithread friendly, but watch out if you are reading/writing files.

If you want to control this aspect through an attribute, let me know.

Registration with Excel

Excel calls this function in your .dll when it starts. The macro generates the register_* functions for you so follow this template. If someone knows how to automatically determine these from a proc-macro, please get in touch, or raise a PR

// For excel to register this XLL ensure `no_mangle` is specified 
#[no_mangle]
pub extern "stdcall" fn xlAutoOpen() -> i32 {
    let reg = Reg::new();
    register_add(&reg);
    1 // Must return 1 to signal to excel SUCCESS
}

xladd dependency

As I cannot seem to be able to get in touch with MarcusRainbow, the original author of the xladd crate, I've created a fork of that, so in Cargo.toml you need to add a github dependency xladd = { git ="https://github.com/ronniec95/xladd"}. Let me know if that is a problem and I can see if there's a better way

Unsafe code & Excel compatibility

As the xladd package calls into the Windows C api it's inherently unsafe. This package uses the LPXLOPER12 api for excel so is compatible with 2007 onwards. If the add-in is not working, check that your excel is the samme bit size (32 or 64bit) as your rustc compiler. Often Excel is installed as 32bit in a lot of organisations and your rustc compiler is probably 64bit. This will natually not work.

Not yet handled

Asynchronous methods. I've not had the need for this especially as network and IO type work is much better done within Excel itself.

I also would like to add RTD support so you can subscribe to live data.

Debugging

Within VSCode you can create a configuration for debugging and change the program to

 "program": "C:/Program Files/Microsoft Office/root/Office16/EXCEL.EXE",

This will launch excel but you can set breakpoints in your code.

xladd-derive's People

Contributors

ronniec95 avatar andrew6ger avatar

Stargazers

João Pedro avatar David Corcoran avatar Dorian Meric avatar  avatar Shabbir Hasan avatar Steven avatar Thomas avatar  avatar langzi.me avatar Pierre Leandre avatar Govert van Drimmelen avatar  avatar  avatar Jens C. avatar MOZGIII avatar Paddy Horan avatar Squirrel avatar

Watchers

Govert van Drimmelen avatar  avatar Ben Briggs avatar

Forkers

shabbirhasan1

xladd-derive's Issues

Readme + tidy

There's a lot in the repo that I suspect isn't needed for xladd derive.

Would it be possible to do a cull and add a readme so it's easier to for others to see what's important and how to use it.

train.rs unused variable `r`

rustc 1.46.0 (04488afe3 2020-08-24)

When following examples/train.rs I am encountering the below compilation warning. This means registration doesn't occur.
It seems registration of non-Array2 functions works fine however.

I would appreciate any advice & thanks in advance 👍

warning: unused variable: `r`
  --> src\lib.rs:58:9
   |
58 |     let r = Reg::new();
   |         ^ help: if this is intentional, prefix it with an underscore: `_r`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.80s

#[cfg(feature = "use_ndarray")]
#[xl_func(category = "OptionPricing", prefix = "my", rename = "baz")]
fn add_f64_2(a: Array2<f64>) -> Result<Array2<f64>, Box<dyn std::error::Error>> {
Ok(Array2::from_elem([2, 2], 0.0f64))
}
// Don't forget to register your functions
#[no_mangle]
pub extern "stdcall" fn xlAutoOpen() -> i32 {
let r = Reg::new();
#[cfg(feature = "use_ndarray")]
register_add_f64_2(&r);
1
}

how to call the function in vba?

thank you for the great crate, can you show a example about how to call the function in vba? I don't know how to call the function, thanks!

Trying to run the example, and get "Array2 does not implement "From<&Variant>""

Hi @ronniec95, I'm trying to use your library to build an Excel addin that will have a function that takes a 2d array of cells, and that also outputs a 2d array of values into the spreadsheet. The Readme seems to indicate that you have tried making this a possibility.

I copy-paste the code from the example in "example/train.rs" into a new project (into the file src/lib.rs), however the example does not compile for me, as I get the following error message:

From Rust-analyzer:

[E0277] the trait bound `ArrayBase<OwnedRepr<String>, Dim<[usize; 2]>>: From<&Variant>` is not satisfied. 
[Note] the trait `From<&Variant>` is not implemented for `ArrayBase<OwnedRepr<String>, Dim<[usize; 2]>>`

Full error from the compiler:

   Compiling xl-stress-loss v0.1.0 (F:\Creations\code\rust\xl-stress-loss)
error[E0277]: the trait bound `ArrayBase<OwnedRepr<String>, Dim<[usize; 2]>>: From<&Variant>` is not satisfied
  --> src\lib.rs:45:1
   |
45 | #[xl_func(category = "OptionPricing", prefix = "my", rename = "bar")]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<&Variant>` is not implemented for `ArrayBase<OwnedRepr<String>, Dim<[usize; 2]>>`
   |
   = help: the following other types implement trait `From<T>`:
             <ArrayBase<CowRepr<'a, A>, D> as From<&'a ArrayBase<S, D>>>
             <ArrayBase<CowRepr<'a, A>, D> as From<ArrayBase<OwnedRepr<A>, D>>>
             <ArrayBase<CowRepr<'a, A>, D> as From<ArrayBase<ViewRepr<&'a A>, D>>>
             <ArrayBase<CowRepr<'a, A>, Dim<[usize; 1]>> as From<&'a Slice>>
             <ArrayBase<OwnedArcRepr<A>, D> as From<ArrayBase<OwnedRepr<A>, D>>>
             <ArrayBase<OwnedRepr<A>, Dim<[usize; 2]>> as From<Vec<V>>>
             <ArrayBase<OwnedRepr<A>, Dim<[usize; 3]>> as From<Vec<V>>>
             <ArrayBase<S, Dim<[usize; 1]>> as From<Box<[A]>>>
           and 5 others
   = note: required for `&Variant` to implement `Into<ArrayBase<OwnedRepr<String>, Dim<[usize; 2]>>>`
   = note: required for `ArrayBase<OwnedRepr<String>, Dim<[usize; 2]>>` to implement `TryFrom<&Variant>`
   = note: this error originates in the attribute macro `xl_func` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `Variant: From<ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>>>` is not satisfied
  --> src\lib.rs:45:1
   |
45 | #[xl_func(category = "OptionPricing", prefix = "my", rename = "bar")]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>>>` is not implemented for `Variant`
   |
   = help: the following other types implement trait `From<T>`:
             <Variant as From<&(&[&str], usize)>>
             <Variant as From<&(&[f64], usize)>>
             <Variant as From<&str>>
             <Variant as From<*mut xloper12>>
             <Variant as From<String>>
             <Variant as From<Vec<&str>>>
             <Variant as From<Vec<(String, f64)>>>
             <Variant as From<Vec<(Variant, f64)>>>
           and 7 others
   = note: this error originates in the attribute macro `xl_func` (in Nightly builds, run with -Z macro-backtrace for more info)

Any clue what is wrong?

Thanks

Excel12v order of arguments

Hi,

Really related to your underlying fork of xladd but was not possible to post issues there. I'm struggling to ensure that usage of the crate will not lead to memory errors since the exccel21v function in entrypoint.rs usses the function call type:

type EXCEL12PROC = extern "stdcall" fn( xlfn: ::std::os::raw::c_int, count: ::std::os::raw::c_int, rgpxloper12: *const LPXLOPER12, xloper12res: LPXLOPER12, ) -> ::std::os::raw::c_int;

Note in particular the order (xlfn, count, result, ...) whereas in xlcall.h it's defined as:

int pascal Excel12v(int xlfn, LPXLOPER12 operRes, int count, LPXLOPER12 opers[]);

Where the order is (xlfn, res, count) which is also how it's document at https://docs.microsoft.com/en-us/office/client-developer/excel/excel4v-excel12v

Am i missing something that makes this work?

Async function crashing

Hi,

Re-opening as a new issue. I am trying to debug this as well but have a limited understanding of the codebase so though I should report it clearly:

Running:

    use xladd::registrator::Reg;
    use xladd::variant::Variant;
    use xladd::xlcall::LPXLOPER12;
    use xladd_derive::xl_func;
    
    #[xl_func(category = "Test", async = "true")]
    fn ra_send(json: String) -> Result<Variant, Box<dyn std::error::Error>> {
        let client = reqwest::blocking::Client::new();
        let resp = client
            .post("http://localhost:8080/post")
            .header(reqwest::header::CONTENT_TYPE, "application/json")
            .body(json)
            .send();
    
        Ok(Variant::from(11.0))
    }
    
    #[no_mangle]
    pub extern "stdcall" fn xlAutoOpen() -> i32 {
        let r = Reg::new();
        register_ra_send(&r);
        1
    }

And then puttting =ra_send("") WITHOUT any server running on localhost:8080 will randomly crash excel - somethimes the cell takes the value 11

Function taking variable type in

Hi Again,

Sorry to keep bothering you - am I correct in uderstanding that the derive function (which provide nice Result<> wrapping!) does not support any input type that holds a variable typed input array / matrix?

I.e. if i in Excel do =my_test(A1:B8)

In the underlying crate I could then define a function that would give me a single LPXLOPER12 / Variant in where i Could iterate over each matrix element and do different things depending on if they contain strings or numbers e.g. (but then without the error wrapping)

I naively tried to derive such a function using:

#[xl_func(category = "test")]
fn ra_json_object(val: Variant) -> Result<Variant, Box<dyn std::error::Error>> {

Without any luck

Bug: Incomplete change to NAN instead of 0.0

Hi again,

I think this is a bug: You have chagned from returning 0.0 to NaN when not a number when coverting to e.g. slices of floats (this is agood change!),

However: I've encountered a case where a input Nan is converted silently to a 0, I think the probmem is this:

    impl From<&xloper12> for f64 {
        fn from(v: &xloper12) -> f64 {
            match v.xltype & xltypeMask {
                xltypeNum => unsafe { v.val.num },
                xltypeInt => unsafe { v.val.w as f64 },
                xltypeStr => 0.0,
                xltypeBool => (unsafe { v.val.xbool == 1 }) as i64 as f64,
                xltypeMulti => unsafe {
                    let p = v.val.array.lparray;
                    f64::from(&*p.offset(0))
                },
                xlerrDiv0 | xlerrNA => f64::NAN,
                _ => 0.0,
            }
        }
    }

where _ is mapped to 0.0 (as well as xltypeStr), should they not all be mapped to NaN when they cannot reasonably be interpreted as numbers?

At the very least xltypeErr should be mapped to NaN since:

impl From<&(&[f64], usize)> for Variant {

Maps a slice of floats using

 impl From<f64> for Variant {

which maps NAN to xlerrNa => a xloper of type xltypeErr

which this later if mapped bac to a Vec has silently converted a slice of numbers including Nans to one where each Nan has been replaced by a 0

Enhancement: From Vec/slice of Variant

The original xladd had a Variant method from_array() which allowed construction for 2d arrays of Variants.

This is replaced by the (generally much nicer) implementation of From trait for various types.

However , without using ndarray there seems to be now way to construct a 2d array of varying types - I.e it would be usefull to have a methof similar to:

impl From<&(&[f64], usize)> for Variant {

But instead for slice of Variants:

impl From<&(&[Variant], usize)> for Variant {

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.