xlsgen supports Rust as a programming language which allows to build rust applications of any size and complexity.
Rust is a functional language popularized in a number of environments and places, and it turns out that it can also make low-level calls. The xlsgen install package takes advantage of this and comes with an interface layer (called xlsgen.rs
) which exposes the entire object model to Rust code environments. Both enumerated types and interfaces are available.
It is assumed that the basic Rust development or runtime environment is installed on the computer. The xlsgen interface layer does not use any particular feature of a recent Rust version, so most likely any Rust version of the language and environment is compatible with it.
In fact, what xlsgen relies on is the Rust winapi crate, which provides a number of Windows types and macros that make it easier to expose and handle.
Here is how a simple Rust program looks like in structure :
- src
|
|- main.rs (your code goes here)
|- xlsgen.rs (xlsgen interface layer (available from the xlsgen install package))
- Cargo.toml (known dependency declaration (winapi))
Here is how you build it :
cd \tmp\rust\simple cargo build
Here is how you run it :
cd \tmp\rust\simple cargo run
During the first run, the package manager will download a compatible version of the winapi
crate in order to meet the dependency specifications, so the compuer where this runs must be online and ready to download stuff, even though this stuff has nothing to do with xlsgen.
xlsgen.rs
on the other hand is part of the xlsgen install package, so it can be copied over in your source code folder structure. xlsgen.rs
is computer-generated by a tool that was designed to convert a COM IDL object model to a Rust compatible object model.
Rust can execute low-level statements, but because of what they are those statements have to be enclosed inside an unsafe
section.
Here is how you put a number in cell B4 :
let _hr = unsafe { (*p_worksheet).put_float(4, 2, 4.5); };
The return value of any call is a HRESULT, i.e. a value of zero means everything went fine, and you can look up winerr.h to find the list of existing HRESULT errors (for instance 0x80070057 = invalid parameter).
Before you can load xlsgen in memory, the COM library must be loaded, which means a paired CoInitialize/CoUninitalize call.
In Rust, all depending types must be declared, tha's why your typical application will include statements such as :
use winapi::{Class, Interface};
Last but not least, Rust macros can help group code patterns together. For instance, if you'd like to create a pointer to an object, initialize to null, you would declare a macro like this :
macro_rules! new_pointer { ($param:ident, $type:ty) => {let mut $param: *mut $type = unsafe { std::mem::zeroed() };} }
And then you would use this macro like this :
new_pointer!(p_engine, xlsgen::IXlsEngine);
Here is a Rust sample application in full that creates a spreadsheet with two sheets, puts a couple numbers in it and reads back the content of a cell. It's available in the /samples folder of the xlsgen install :
use winapi::um::winnt::{LPWSTR}; use winapi::{Class, Interface}; use std::ffi::{OsStr, OsString}; use std::os::windows::ffi::OsStrExt; use std::os::windows::ffi::OsStringExt; use std::slice; pub mod xlsgen; // *** MACROS begin here *** macro_rules! retVal { ($param:ident) => {&mut $param as *mut _ as *mut _} } macro_rules! new_pointer { ($param:ident, $type:ty) => {let mut $param: *mut $type = unsafe { std::mem::zeroed() };} } macro_rules! release_pointer { ($param:ident) => { unsafe { (&mut * $param).Release() }; } } macro_rules! release_bstr_string { ($param:ident) => { unsafe { winapi::um::oleauto::SysFreeString( $param as LPWSTR ) }; } } // *** MACROS end here *** // unicode strings (Rust stores strings using UTF8 encoding, not UTF16) so we have to convert back and forth fn to_wstring(string: &str) -> Vec<u16> { let mut v: Vec<u16> = OsStr::new(string).encode_wide().collect(); v.push(0); // EOL v } fn from_wstring(string: LPWSTR) -> String { let string_slice = unsafe { let len = winapi::um::oleauto::SysStringLen(string); slice::from_raw_parts(string, len as usize) }; OsString::from_wide(string_slice).into_string().unwrap() } fn main() { println!("step1"); let _hr = unsafe { winapi::um::objbase::CoInitialize(std::ptr::null_mut()) }; println!("step2"); new_pointer!(p_engine, xlsgen::IXlsEngine); let _hr = unsafe { winapi::um::combaseapi::CoCreateInstance( &xlsgen::XlsEngine::uuidof(), std::ptr::null_mut(), winapi::shared::wtypesbase::CLSCTX_INPROC_SERVER, &xlsgen::IXlsEngine::uuidof(), retVal!(p_engine)) }; println!("step3 {}", _hr); new_pointer!(p_workbook, xlsgen::IXlsWorkbook); let _hr = unsafe { (*p_engine).new(to_wstring("d:\\tmp\\eer.xlsx").as_ptr(), retVal!(p_workbook)) }; new_pointer!(p_worksheet1, xlsgen::IXlsWorksheet); let _hr = unsafe { (*p_workbook).addworksheet(to_wstring("sheet 1").as_ptr(), retVal!(p_worksheet1)) }; new_pointer!(p_worksheet2, xlsgen::IXlsWorksheet); let _hr = unsafe { (*p_workbook).addworksheet(to_wstring("sheet 2").as_ptr(), retVal!(p_worksheet2)) }; println!("step4"); let mut i_wksht_count : i32 = 0; let _hr = unsafe { (*p_workbook).get_worksheetcount(&mut i_wksht_count) }; println!("step4a {}, wkshtCount = {}", _hr, i_wksht_count); unsafe { (*p_workbook).put_factorizedstringsmode(true) }; let mut b_factorized : bool = false; let _hr = unsafe { (*p_workbook).get_factorizedstringsmode(&mut b_factorized) }; println!("step4ab {}, bFactorized = {}", _hr, b_factorized); // write some content for r in 1..10 { let _hr = unsafe { (*p_worksheet1).put_label(r, 2, to_wstring("hello world").as_ptr()) }; } // read some content println!("step4b"); new_pointer!(cell_string, LPWSTR); println!("step4c"); let _hr = unsafe { (*p_worksheet1).get_label(3, 2, retVal!(cell_string)) }; println!("step5"); println!("cell value is : {}", from_wstring(cell_string as LPWSTR)); println!("step6"); new_pointer!(p_richlabel, xlsgen::IXlsRichLabel); println!("step6b"); let _hr = unsafe { (*p_worksheet1).newrichlabel(retVal!(p_richlabel)) }; println!("step6c {}", _hr); let _hr = unsafe { (*p_richlabel).put_htmllabel(to_wstring("<span>hello <b>world</b></span>").as_ptr()) }; println!("step6d"); let _hr = unsafe { (*p_worksheet1).put_richlabel(1, 4, p_richlabel) }; println!("step6e"); let _hr = unsafe { (*p_workbook).close(); }; println!("step7"); release_bstr_string!(cell_string); release_pointer!(p_worksheet1); release_pointer!(p_worksheet2); release_pointer!(p_workbook); release_pointer!(p_engine); unsafe { winapi::um::combaseapi::CoUninitialize() }; println!("step8"); }
Here is an excerpt from xlsgen.rs
:
(...) RIDL!{#[uuid(0xD97500CF,0x37CC,0x48fd,0x87,0x72,0x0E,0xBC,0x8F,0x8A,0x93,0x76)] interface IXlsEngine(IXlsEngineVtbl): IDispatch(IDispatchVtbl) { fn new( // creates a new .xls/.xlsx file with the name (filename or filepath) passed in parameter. excelfilename : LPCWSTR, workbook : *mut *mut IXlsWorkbook, ) -> HRESULT, (...) RIDL!{#[uuid(0x12B7B224,0xC026,0x4eb6,0xBA,0x94,0xC4,0x9B,0x91,0x6F,0x77,0x40)] interface IXlsWorkbook(IXlsWorkbookVtbl): IDispatch(IDispatchVtbl) { fn addworksheet( // creates a new worksheet with the name passed in parameter. The following characters are forbidden : / \\ : * [ ], and the name must be less than 32 characters long. name : LPCWSTR, worksheet : *mut *mut IXlsWorksheet, ) -> HRESULT, (...) RIDL!{#[uuid(0xB150D9DA,0x1F10,0x4850,0x9A,0xBB,0x76,0xD9,0x95,0x47,0x96,0xEE)] interface IXlsWorksheet(IXlsWorksheetVtbl): IDispatch(IDispatchVtbl) { fn put_label( // puts a label in a cell. row : i32, col : i32, label : LPCWSTR, ) -> HRESULT,