Skip to content

Commit d1805e2

Browse files
committed
Proper implementation of get_cell_value_by_index.
1 parent 127dfd6 commit d1805e2

File tree

6 files changed

+207
-43
lines changed

6 files changed

+207
-43
lines changed

bindings/dotnet/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ fn main() -> Result<(), Box<dyn Error>> {
44
csbindgen::Builder::default()
55
.input_extern_file("src/lib.rs")
66
.csharp_class_name("NativeMethods")
7-
.csharp_namespace("IronCalc")
7+
.csharp_namespace("IronCalc.Native")
88
.csharp_dll_name("libironcalc_dotnet")
99
.csharp_use_function_pointer(true)
1010
.csharp_generate_const_filter(|_| true)

bindings/dotnet/ironcalc-dotnet/IronCalc.Tests/EvaluateTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ public void Sum()
2121
using var model = Model.LoadFromXlsxBytes(bytes, "en", "Europe/Oslo");
2222

2323
var value = model.GetValue(0, 3, 1);
24-
Assert.Equal(2, value);
24+
var number = Assert.IsType<CellValue.Number>(value);
25+
Assert.Equal(2, number.Value);
2526

2627
model.SetUserInput(0, 1, 1, "4");
2728
model.SetUserInput(0, 2, 1, "6");
2829
model.Evaluate();
2930

3031
var updated= model.GetValue(0, 3, 1);
31-
Assert.Equal(10, updated);
32+
var updatedNumber = Assert.IsType<CellValue.Number>(updated);
33+
Assert.Equal(10, updatedNumber.Value);
3234
}
3335
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace IronCalc;
2+
3+
public abstract class CellValue
4+
{
5+
private CellValue()
6+
{
7+
}
8+
9+
public class None : CellValue;
10+
11+
public class Number : CellValue
12+
{
13+
public required double Value { get; init; }
14+
}
15+
16+
public class Bool : CellValue
17+
{
18+
public required bool Value { get; init; }
19+
}
20+
21+
public class String : CellValue
22+
{
23+
public required string Value { get; init; }
24+
}
25+
}

bindings/dotnet/ironcalc-dotnet/IronCalc/IronCalc.g.cs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
using System.Runtime.InteropServices;
99

1010

11-
namespace IronCalc
11+
namespace IronCalc.Native
1212
{
1313
internal static unsafe partial class NativeMethods
1414
{
@@ -27,12 +27,15 @@ internal static unsafe partial class NativeMethods
2727
[DllImport(__DllName, EntryPoint = "evaluate", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
2828
internal static extern void evaluate(ModelContext* context);
2929

30-
[DllImport(__DllName, EntryPoint = "get_value", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
31-
internal static extern int get_value(ModelContext* context, int sheet, int row, int col);
30+
[DllImport(__DllName, EntryPoint = "get_cell_value_by_index", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
31+
internal static extern GetValueResult get_cell_value_by_index(ModelContext* context, uint sheet, int row, int col);
3232

3333
[DllImport(__DllName, EntryPoint = "set_user_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
3434
internal static extern ModelContextError* set_user_input(ModelContext* context, uint sheet, int row, int col, byte* value);
3535

36+
[DllImport(__DllName, EntryPoint = "dispose_cell_value", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
37+
internal static extern void dispose_cell_value(CellValue* value);
38+
3639
[DllImport(__DllName, EntryPoint = "dispose_error", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
3740
internal static extern void dispose_error(ModelContextError* error);
3841

@@ -63,12 +66,38 @@ internal unsafe partial struct CreateModelContextResult
6366
[MarshalAs(UnmanagedType.U1)] public bool is_ok;
6467
}
6568

69+
[StructLayout(LayoutKind.Sequential)]
70+
internal unsafe partial struct CellValue
71+
{
72+
public CellValueTag tag;
73+
public byte* string_value;
74+
public double number_value;
75+
[MarshalAs(UnmanagedType.U1)] public bool boolean_value;
76+
}
77+
78+
[StructLayout(LayoutKind.Sequential)]
79+
internal unsafe partial struct GetValueResult
80+
{
81+
public CellValue* value;
82+
public ModelContextError* error;
83+
[MarshalAs(UnmanagedType.U1)] public bool is_ok;
84+
}
85+
6686

6787
internal enum ModelContextErrorTag : uint
6888
{
6989
XlsxError = 1,
7090
WorkbookError = 2,
7191
SetUserInputError = 3,
92+
GetUserInputError = 4,
93+
}
94+
95+
internal enum CellValueTag : uint
96+
{
97+
None = 0,
98+
String = 1,
99+
Number = 2,
100+
Boolean = 3,
72101
}
73102

74103

bindings/dotnet/ironcalc-dotnet/IronCalc/Model.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Runtime.CompilerServices;
33
using System.Text;
4+
using IronCalc.Native;
45

56
namespace IronCalc;
67

@@ -22,6 +23,7 @@ internal IronCalcException(string message, ModelContextErrorTag? tag)
2223
ModelContextErrorTag.XlsxError => ErrorCode.XslxError,
2324
ModelContextErrorTag.WorkbookError => ErrorCode.WorkbookError,
2425
ModelContextErrorTag.SetUserInputError => ErrorCode.SetUserInputError,
26+
ModelContextErrorTag.GetUserInputError => ErrorCode.GetUserInputError,
2527
_ => throw new ArgumentOutOfRangeException(nameof(tag), tag, null)
2628
};
2729
}
@@ -33,6 +35,7 @@ public enum ErrorCode
3335
XslxError = 2,
3436
WorkbookError = 3,
3537
SetUserInputError = 4,
38+
GetUserInputError = 5,
3639
}
3740

3841
public class Model : IDisposable
@@ -97,11 +100,46 @@ public void Evaluate()
97100
}
98101
}
99102

100-
public int GetValue(int sheet, int row, int column)
103+
public CellValue GetValue(uint sheet, int row, int column)
101104
{
102105
unsafe
103106
{
104-
return NativeMethods.get_value(ctx, sheet, row, column);
107+
var result = NativeMethods.get_cell_value_by_index(ctx, sheet, row, column);
108+
if (!result.is_ok)
109+
{
110+
throw CreateExceptionFromError(result.error);
111+
}
112+
113+
try
114+
{
115+
switch (result.value->tag)
116+
{
117+
case CellValueTag.None:
118+
return new CellValue.None();
119+
case CellValueTag.String:
120+
var value = new String((sbyte*)result.value->string_value);
121+
return new CellValue.String()
122+
{
123+
Value = value,
124+
};
125+
case CellValueTag.Number:
126+
return new CellValue.Number()
127+
{
128+
Value = result.value->number_value,
129+
};
130+
case CellValueTag.Boolean:
131+
return new CellValue.Bool()
132+
{
133+
Value = result.value->boolean_value,
134+
};
135+
default:
136+
throw new ArgumentOutOfRangeException();
137+
}
138+
}
139+
finally
140+
{
141+
NativeMethods.dispose_cell_value(result.value);
142+
}
105143
}
106144
}
107145

bindings/dotnet/src/lib.rs

Lines changed: 105 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ironcalc::base::cell::CellValue;
1+
use ironcalc::base::cell;
22
use ironcalc::base::Model;
33
use std::ffi::{c_char, CString};
44

@@ -10,6 +10,7 @@ pub enum ModelContextErrorTag {
1010
XlsxError = 1,
1111
WorkbookError = 2,
1212
SetUserInputError = 3,
13+
GetUserInputError = 4,
1314
}
1415

1516
#[repr(C)]
@@ -26,21 +27,23 @@ pub struct CreateModelContextResult {
2627
is_ok: bool,
2728
}
2829

29-
impl CreateModelContextResult {
30-
fn create_error(message: String, tag: ModelContextErrorTag) -> CreateModelContextResult {
31-
let message_ptr = CString::new(message)
32-
.expect("Couldn't create CString")
33-
.into_raw();
30+
fn create_error(message: String, tag: ModelContextErrorTag) -> *mut ModelContextError {
31+
let message_ptr = CString::new(message)
32+
.expect("Couldn't create CString")
33+
.into_raw();
3434

35-
let error = Box::into_raw(Box::new(ModelContextError {
36-
tag,
37-
has_message: true,
38-
message: message_ptr,
39-
}));
35+
Box::into_raw(Box::new(ModelContextError {
36+
tag,
37+
has_message: true,
38+
message: message_ptr,
39+
}))
40+
}
4041

42+
impl CreateModelContextResult {
43+
fn create_error(message: String, tag: ModelContextErrorTag) -> CreateModelContextResult {
4144
CreateModelContextResult {
4245
model: std::ptr::null_mut(),
43-
error,
46+
error: create_error(message, tag),
4447
is_ok: false,
4548
}
4649
}
@@ -141,27 +144,93 @@ pub unsafe extern "C" fn evaluate(context: *mut ModelContext) {
141144
Box::into_raw(ctx) as *mut ModelContext;
142145
}
143146

147+
#[repr(C)]
148+
#[derive(PartialEq)]
149+
pub enum CellValueTag {
150+
None = 0,
151+
String = 1,
152+
Number = 2,
153+
Boolean = 3,
154+
}
155+
156+
#[repr(C)]
157+
pub struct CellValue {
158+
tag: CellValueTag,
159+
string_value: *const c_char,
160+
number_value: f64,
161+
boolean_value: bool,
162+
}
163+
164+
#[repr(C)]
165+
pub struct GetValueResult {
166+
value: *mut CellValue,
167+
error: *mut ModelContextError,
168+
is_ok: bool,
169+
}
170+
144171
#[no_mangle]
145-
pub unsafe extern "C" fn get_value(
172+
pub unsafe extern "C" fn get_cell_value_by_index(
146173
context: *mut ModelContext,
147-
sheet: i32,
174+
sheet: u32,
148175
row: i32,
149176
col: i32,
150-
) -> i32 {
177+
) -> GetValueResult {
151178
let ctx = Box::from_raw(context as *mut InternalModelContext);
152179

153-
let value = ctx
154-
.model
155-
.get_cell_value_by_index(sheet as u32, row, col)
156-
.expect("couldn't get sheet");
157-
let return_value = match value {
158-
CellValue::Number(x) => x as i32,
159-
_ => 0,
180+
let value = match ctx.model.get_cell_value_by_index(sheet, row, col) {
181+
Ok(value) => value,
182+
Err(message) => {
183+
let error = create_error(message, ModelContextErrorTag::GetUserInputError);
184+
Box::into_raw(ctx) as *mut ModelContext;
185+
return GetValueResult {
186+
value: std::ptr::null_mut(),
187+
error,
188+
is_ok: false,
189+
};
190+
}
191+
};
192+
193+
let cell_value = match value {
194+
cell::CellValue::Number(value) => CellValue {
195+
tag: CellValueTag::Number,
196+
string_value: Default::default(),
197+
number_value: value,
198+
boolean_value: Default::default(),
199+
},
200+
cell::CellValue::None => CellValue {
201+
tag: CellValueTag::None,
202+
string_value: Default::default(),
203+
number_value: Default::default(),
204+
boolean_value: Default::default(),
205+
},
206+
cell::CellValue::Boolean(value) => CellValue {
207+
tag: CellValueTag::Boolean,
208+
string_value: Default::default(),
209+
number_value: Default::default(),
210+
boolean_value: value,
211+
},
212+
cell::CellValue::String(value) => {
213+
let value_ptr = CString::new(value)
214+
.expect("Couldn't create CString")
215+
.into_raw();
216+
CellValue {
217+
tag: CellValueTag::String,
218+
string_value: value_ptr,
219+
number_value: Default::default(),
220+
boolean_value: Default::default(),
221+
}
222+
}
160223
};
161224

225+
let cell_value = Box::into_raw(Box::new(cell_value));
226+
162227
Box::into_raw(ctx) as *mut ModelContext;
163228

164-
return_value
229+
GetValueResult {
230+
value: cell_value,
231+
error: std::ptr::null_mut(),
232+
is_ok: true,
233+
}
165234
}
166235

167236
#[no_mangle]
@@ -173,21 +242,13 @@ pub unsafe extern "C" fn set_user_input(
173242
value: *const c_char,
174243
) -> *mut ModelContextError {
175244
let mut ctx = Box::from_raw(context as *mut InternalModelContext);
176-
let value = std::ffi::CStr::from_ptr(value).to_string_lossy().to_string();
245+
let value = std::ffi::CStr::from_ptr(value)
246+
.to_string_lossy()
247+
.to_string();
177248

178249
if let Err(message) = ctx.model.set_user_input(sheet, row, col, value) {
179-
let message_ptr = CString::new(message)
180-
.expect("Couldn't create CString")
181-
.into_raw();
182-
183-
let error = Box::into_raw(Box::new(ModelContextError {
184-
tag: ModelContextErrorTag::SetUserInputError,
185-
has_message: true,
186-
message: message_ptr,
187-
}));
188-
250+
let error = create_error(message, ModelContextErrorTag::SetUserInputError);
189251
Box::into_raw(ctx) as *mut ModelContext;
190-
191252
return error;
192253
}
193254

@@ -196,6 +257,15 @@ pub unsafe extern "C" fn set_user_input(
196257
std::ptr::null_mut()
197258
}
198259

260+
#[no_mangle]
261+
pub unsafe extern "C" fn dispose_cell_value(value: *mut CellValue) {
262+
let value = Box::from_raw(value);
263+
if value.tag == CellValueTag::String {
264+
let str = value.string_value as *mut c_char;
265+
_ = CString::from_raw(str)
266+
}
267+
}
268+
199269
#[no_mangle]
200270
pub unsafe extern "C" fn dispose_error(error: *mut ModelContextError) {
201271
let error = Box::from_raw(error);

0 commit comments

Comments
 (0)