This post is related to https://forum.nim-lang.org/t/2977/2, in which I said "For commercial products to read/write XLS/XLSX, I found http://libxl.com/"
So I started this binding work on June 18th 2017.
However I am totally newbee in fact of Nim and I have not so much free time, so
the first question, I translated
typedef struct tagBookHandle * BookHandle;
to
type
tagBookHandle* {.final, incompleteStruct, importc: "tagBookHandle".} = object
BookHandle* {.importc: "BookHandle".} = ptr tagBookHandle
Am I right?
But why I get
unknown type name 'tagBookHandle'
when I compile the following simple code
type
tagBookHandle* {.final, incompleteStruct, importc: "tagBookHandle".} = object
BookHandle* {.importc: "BookHandle".} = ptr tagBookHandle
const
xldll* = "libxl.dll"
# BookHandle xlCreateBookCW(void);
proc xlCreateBookCW*(): BookHandle {.cdecl, importc: "xlCreateBookCW", dynlib: xldll.}
So I started this binding work on June 18th 2017.
While c2nim does a very good job in binding generation, it is helpful if one has some C knowledge and additional has a basic understanding of the library to wrap, because it can be hard to guess what C header files really intent.
typedef struct tagBookHandle * BookHandle;
seems to be a Opaque C structs, see https://stackoverflow.com/questions/3965279/opaque-c-structs-how-should-they-be-declared
So tagBookHandle symbol may be hidden in the .c file and not visible in the headers. So importc: "tagBookHandle" would fail.
Does c2nim really generate above wrapper fragment? I think importc is generally not generated for types, and I can not remember getting a incompleteStruct pragma for my GTK3 related wrappers.
Maybe, if you have not much time and need that library for a commercial product, then you can hire someone with good C and Nim background.
this is what I get currently. Most functions in libxl can be exposed in the same way
But something still puzzled me, or I have not know yet in nim:
import encodings
type
tagBookHandle* {.final, header: "<handle.h>", importc: "struct tagBookHandle".} = object
BookHandle* = ptr tagBookHandle
type
tagSheetHandle* {.final, header: "<handle.h>", importc: "struct tagSheetHandle".} = object
SheetHandle* = ptr tagSheetHandle
const NUMFORMAT_DATE = 14
const
xldll* = "libxl.dll"
proc xlCreateBookCW*(): BookHandle {.cdecl, importc: "xlCreateBookCW", dynlib: xldll.}
proc xlBookAddSheetW*(handle: BookHandle, name: cstring, initSheet: int|SheetHandle): SheetHandle {.cdecl,
importc: "xlBookAddSheetW", dynlib: xldll.}
proc xlSheetWriteStrW*(handle: SheetHandle; row: cint; col: cint; value: Widecstring;
format: cint): cint {.cdecl,
importc: "xlSheetWriteStrW", dynlib: xldll, discardable.}
proc xlBookSaveW*(handle: BookHandle; filename: Widecstring): cint {.cdecl,
importc: "xlBookSaveW", dynlib: xldll, discardable.}
proc xlBookReleaseW*(handle: BookHandle) {.cdecl, importc: "xlBookReleaseW",
dynlib: xldll.}
var book = xlCreateBookCW()
if not book.isnil:
var sheet = xlBookAddSheetW(book, "Sheet1", 0);
if not sheet.isnil:
xlSheetWriteStrW(sheet,
cint(2), cint(1),
newWideCString(convert("你好!", "utf8", "gb2312")),
cint(0)
);
if not (xlBookSaveW(book, newWideCString(convert("测试.xls", "utf8", "gb2312")))==0):
echo "\nFile example.xls has been created.\n";
xlBookReleaseW(book);
Sorry, no idea for your widecstring and unicode questions. How is that handled from C?
Your cint conversion should be not necessary, the compiler will pass int literals correctly to library functions, so
xlSheetWriteStrW(sheet, 2, 1,
#xlSheetWriteStrW(sheet, cint(2), cint(1),
should work
thanks, and I found
echo type("text") is string
but the following code seems to always think `not (type(value) is WideCString)` is true.
import encodings
proc CxlSheetWriteStrW(row: cint; col: cint; value: Widecstring;
format: cint): cint {.discardable.} =
echo row, col, value, format
proc xlSheetWriteStrW(row: cint; col: cint; value: string|Widecstring;
format: cint): cint {.discardable.} =
var value1: WideCString
if not (type(value) is WideCString):
echo "not widecstring"
value1 = newWideCString(convert(value, "gb2312", "utf8"))
else:
echo "widecstring"
value1 = value
echo value1 #, $type(value)
return CxlSheetWriteStrW(row, col, value1, format)
var i = newWideCString(convert("text", "gb2312", "utf8"))
echo i
echo type(i) is WideCString # this says true
echo type(i) is cString # this says false
xlSheetWriteStrW(1, 2, i, 3)
msg is
c.nim(25, 17) template/generic instantiation from here
c.nim(12, 40) Error: type mismatch: got (WideCString, string, string)
but expected one of:
proc convert(s: string; destEncoding = "UTF-8"; srcEncoding = "CP1252"): string
proc convert(c: EncodingConverter; s: string): string
more question I, a lazy none programmer practitioner, met: How to make an auto-converted type? Sorry, I don't know its exact name. I will explain it as folows:
for example, in the original functions in DLL, Widecstring type arg is need. So the actual code should be
proc original_Clang_FunctionA(value: Widecstring) {.cdecl,
importc: "functon1", dynlib: xldll}.
...
proc original_Clang_FunctionZ(value: Widecstring) {.cdecl,
importc: "functon26", dynlib: xldll}.
var value1 = newWideCString(convert("你好", "utf8", "gb2312"))
original_Clang_FunctionA(value1)
or we can make a function
proc s(val: string|Widecstring): Widecstring =
when not type(val) is Widecstring:
return newWideCString(convert(val, "utf8", "gb2312"))
else:
return val
then user writes
original_Clang_FunctionA("你好".s)
...
original_Clang_FunctionZ("世界".s)
...
both are tedious for user.
So We can wrap the DLL function like this:
proc original_Clang_FunctionA(value: Widecstring) {.cdecl,
importc: "functon26", dynlib: xldll}.
proc FunctionA(value: string|Widecstring) =
var value1 = value
when not type(value1) is Widecstring:
value1 = newWideCString(convert(value1, "utf8", "gb2312"))
original_Clang_FunctionA(value1)
then the user only need to write in a clean way:
FunctionA("你好")
However the problem is that we have to supply every wrapped functions for so many functions, which is a repetitive work for library supplier.
So if there is something like this
type
someString from object(string|Widecstring) =
when not type(someString) is Widecstring:
someString.value = newWideCString(convert(value1, "utf8", "gb2312"))
someString.type = Widecstring
proc FunctionA(value: someString) {.cdecl,
importc: "functon1", dynlib: xldll}.
...
proc FunctionZ(value: someString) {.cdecl,
importc: "functon26", dynlib: xldll}.
and the user only writes like before
FunctionA("你好")
...
FunctionA("世界")
is this possible? Thanks still be Greek for me.
Well, the when statement is really easy to understand and well explained in the manual, so maybe you should read it again. It is good to understand it.
What you may be looking for is a converter, see manual. A converter allows automatic conversion of proc parameter types. So you may define a converter which converts cstrings to cwidestrings, and then whenever there is a proc that needs a cwidestring and you pass it a cstring, then that converter is called automatically and does the needed conversion. Note, that use of very many converters may make compilation slower, as the compiler has much effort to select all the right converters.
For your code example
proc s(val: string|Widecstring): Widecstring =
when not type(val) is Widecstring:
return newWideCString(convert(val, "utf8", "gb2312"))
else:
return val
I am not sure if that shape make much sense. As Nim is statically typed, a proc signature like "proc s(val: string|Widecstring): Widecstring =" generates two procs, one with string, the other with Widecstring argument. Due to the when statement, the later really does nothing. What you may want here is just a proc or converter that converts from string to WideCString or whatever.
Hello @oyster
Ssorry to ask, but how is the process going with the libxl binding ?
I need to work with a project where reading and writing Excel files will be an important prerequisite ... figuring that there is not yet a component in Nim to get the job done, I will have to perform the delivery using Python.
Anyway, despite not having a very advanced knowledge of Nim, if I can help in anything with its development, please let me know.
I'm sure more people here in our group will benefit from being able to do some CRUD in .xlsx files directly from Nim.
Cheers
@alfrednewman
It might be possible to wrap a Python XLS library in Nim?.
It's an approach I'm considering for some python libs I'm having trouble porting to Nim due to my lack of low-level C coding ability - eg Pyglet.
It's not entirely unprecedented, Haskell also wraps some awesome python libs to bring some of that magic into their ecosystem:
https://hackage.haskell.org/package/matplotlib
Edit: I see that someone also made a Nim wrapper for matplotlib:
https://github.com/bluenote10/nim-matplotlib
It should be fairly easy to borrow and adapt that example for XLS?
Both of those libs work by building a Python script file via native (Nim or Haskell) functions, and then at the end running the Python script through the standard Python interpreter.
Though, for something more interactive; rather than batch-job like, there are also some Nim libs for wrapping the python lib runtime.
May be better to try to make your own really basic wrapper in the meanwhile, and put that in github :-)
For some ideas and inspiration you can watch the "c2nim" section of Araq's 2 hour Nim Workshop YouTube vid :-)
Aha, I almost modified nothing, then my code can be compiled with nim 1.7.1, and the xls file is created as expected! What a weird fantacy!
import encodings
type
tagBookHandle* {.final, header: "<handle.h>", importc: "struct tagBookHandle".} = object
BookHandle* = ptr tagBookHandle
type
tagSheetHandle* {.final, header: "<handle.h>", importc: "struct tagSheetHandle".} = object
SheetHandle* = ptr tagSheetHandle
const NUMFORMAT_DATE = 14
const
xldll* = "libxl.dll"
proc xlCreateBookCW*(): BookHandle {.cdecl, importc: "xlCreateBookCW", dynlib: xldll.}
proc xlBookAddSheetW*(handle: BookHandle, name: Widecstring, initSheet: int|SheetHandle): SheetHandle {.cdecl,
importc: "xlBookAddSheetW", dynlib: xldll.}
proc xlSheetWriteStrW*(handle: SheetHandle; row: cint; col: cint; value: Widecstring;
format: cint): cint {.cdecl,
importc: "xlSheetWriteStrW", dynlib: xldll, discardable.}
proc xlBookSaveW*(handle: BookHandle; filename: Widecstring): cint {.cdecl,
importc: "xlBookSaveW", dynlib: xldll, discardable.}
proc xlBookReleaseW*(handle: BookHandle) {.cdecl, importc: "xlBookReleaseW",
dynlib: xldll.}
var book = xlCreateBookCW()
if not book.isnil:
var sheet = xlBookAddSheetW(book, newWideCString("表1"), 0);
if not sheet.isnil:
xlSheetWriteStrW(sheet,
cint(2), cint(1),
newWideCString("你好!"),
cint(0)
);
if not (xlBookSaveW(book, newWideCString("测试.xls"))==0):
echo "\nFile example.xls has been created.\n";
xlBookReleaseW(book);
BTW, there is another version which is translated from VB6's declaration. No handle.h is need as the above version does. However, maybe it works only on windows (or windows 64 bits?)
import encodings
import strformat
type
Integer = int16
Long = int64
Double = cdouble
String = Widecstring
const
xldll* = "libxl.dll"
proc xlCreateBook*(): Long {.cdecl, importc: "xlCreateBookW", dynlib: xldll, discardable.}
proc xlBookLoad*(bookHandle: Long, filename: String): Integer {.cdecl, importc: "xlBookLoadW", dynlib: xldll, discardable.}
proc xlBookSave*(bookHandle: Long, filename: String): Integer {.cdecl, importc: "xlBookSaveW", dynlib: xldll, discardable.}
proc xlBookGetSheet*(bookHandle: Long, index: Integer): Long {.cdecl, importc: "xlBookGetSheetW", dynlib: xldll, discardable.}
proc xlSheetReadStr*(sheetHandle: Long, row: Integer, col: Integer, format: Long): String {.cdecl, importc: "xlSheetReadStrW", dynlib: xldll, discardable.}
proc xlSheetWriteStr*(sheetHandle: Long, row: Integer, col: Integer, value: String, formatHandle: Long): Integer {.cdecl, importc: "xlSheetWriteStrW", dynlib: xldll, discardable.}
proc xlSheetReadNum*(sheetHandle: Long, row: Integer, col: Integer, format: Long): Double {.cdecl, importc: "xlSheetReadNumW", dynlib: xldll, discardable.}
proc xlSheetWriteNum*(sheetHandle: Long, row: Integer, col: Integer, value: Double, formatHandle: Long): Integer {.cdecl, importc: "xlSheetWriteNumW", dynlib: xldll, discardable.}
proc xlBookRelease*(bookHandle: Long): void {.cdecl, importc: "xlBookReleaseW", dynlib: xldll.}
var book = xlCreateBook();
if book > 0:
if xlBookLoad(book, newWideCString("example.xls")) > 0 :
var sheet = xlBookGetSheet(book, 0);
if sheet > 0:
var s = xlSheetReadStr(sheet, 2, 1, 0);
if len(s) > 0:
echo(fmt"{s}");
var d = xlSheetReadNum(sheet, 3, 1, 0);
echo(fmt"{d}");
xlSheetWriteNum(sheet, 3, 2, 3.1415926, 0);
var st = xlSheetReadStr(sheet, 3, 1, 0);
echo(fmt"{st}");
xlSheetWriteStr(sheet, 3, 1, newWideCString("汉字"), 0);
if(xlBookSave(book, newWideCString("out.xls"))>0):
echo("\nFile example.xls has been modified.\n");
xlBookRelease(book);