I'm writing (yet another) wrapper - this time for: https://github.com/jtanx/libclipboard
My code is here: https://github.com/arturo-lang/arturo/blob/improve-webview-and-ui/src/extras/libclipboard.nim
The relevant part being:
type
ClipboardObj* {.importc: "clipboard_c", header:"libclipboard/libclipboard.h".} = pointer
The problem - I guess - is that clipboard_c is declared as a forward declaration in libclipboard.h like:
/** Opaque data structure for a clipboard context/instance **/
typedef struct clipboard_c clipboard_c;
And thus I keep getting:
/@[email protected]:57:21: error: variable has incomplete type 'struct clipboard_c'
struct clipboard_c clipboard;
^
/Users/drkameleon/Documents/Code/OpenSource/arturo-lang/arturo/src/extras/libclipboard/libclipboard.h:148:16: note: forward declaration of 'struct clipboard_c'
typedef struct clipboard_c clipboard_c;
I've tried declaring it as an object, even with .incompleteStruct but still not sure how to get around this.
Any ideas?
For anyone that might come across a similar issue, I - hope I - fixed it by declaring ClipboardObj as:
ClipboardObj* {.pure.} = ref object
P.S. Despite the fact that pragmas themselves have always looked a bit "hack-ish" to me, I'm perfectly fine with them. That being said, it would be great if they were thoroughly documented somewhere - each and every single one of them - so that we don't have to simply guess what they mean, or go through the whole Nim codebase (or examples) to simply find one being used by chance... My .2 cents. :)
You have some point - I have been looking at this part, although I did get it to compile.
Any alternative suggestions?
If you use C library or API, I think you should learn C language. If you know C, you would be able to solve such a problem yourself. If you wrap C library/API without knowing C language, you might write unsafe code.
It seems there people trying to use C library/API with Nim without knowing C language. If we don't tell them to learn C, they will keep asking for C questions.
clipboard_c is an incomplete struct type and you always use pointer to clipboard_c, but never declare clipboard_c variable. Also you cannot use sizeof(clipboard_c). You can use functions in the library with pointer to incomplete struct.
lol Barkin up the wrong tree. After 28 years of programming, I guess I must have written my fair share of C, no? (As some unrelated trivia, Arturo was initially written completely in C).
The question had mainly to do with Nim and how it translates things to C. Because knowing how to write something in C is one thing, having to guess how something will be translated to C is totally different.
For anyone that might care, the update version:
#=======================================
# Compilation & Linking
#=======================================
{.passC: "-I" & parentDir(currentSourcePath()) .}
{.compile("libclipboard/clipboard_common.c", "-I" & parentDir(currentSourcePath())).}
when defined(linux) or defined(freebsd):
{.compile("libclipboard/clipboard_x11.c", "-DLIBCLIPBOARD_BUILD_X11 -pthread").}
{.passL: "-pthread -lxcb".}
elif defined(macosx):
{.compile("libclipboard/clipboard_cocoa.c", "-x objective-c -DLIBCLIPBOARD_BUILD_COCOA -framework Foundation").}
elif defined(windows):
{.compile("libclipboard/clipboard_win32.c", "-DLIBCLIPBOARD_BUILD_WIN32").}
#=======================================
# Types
#=======================================
type
ClipboardMode* {.size: sizeof(cint).} = enum
LCB_CLIPBOARD = 0
LCB_PRIMARY = 1
LCB_SELECTION = 2
LCB_SECONDARY = 3
LCB_MODE_END = 4
ClipboardStruct* {.importc:"clipboard_c", header: "libclipboard/libclipboard.h", pure.} = object
ClipboardObj* = ptr ClipboardStruct
#=======================================
# Function prototypes
#=======================================
{.push header: "libclipboard/libclipboard.h", cdecl.}
proc clipboard_new*(cb_opts: pointer): ClipboardObj {.importc.}
proc clipboard_clear*(cb: ClipboardObj, mode: ClipboardMode) {.importc.}
proc clipboard_free*(cb: ClipboardObj) {.importc.}
proc clipboard_set_text*(cb: ClipboardObj, src: cstring) {.importc.}
proc clipboard_text*(cb: ClipboardObj): cstring {.importc.}
{.pop.}
Getting the Nim -> C wrapping right can be a bit of a challenge, but once you know what's going on under the hood it's easy to see what you did wrong. So let's dive into what goes on here.
First of you started with
type ClipboardObj* {.importc: "clipboard_c", header:"libclipboard/libclipboard.h".} = pointer
This tells Nim that whenever it sees the use of ClipboardObj it should replace it with clipboard_c and otherwise treat it as a pointer in Nim code. This means that code like this:
var clipboard = clipboard_new(nil)
roughly translates to:
clipboard_c clipboard;
clipboard = clipboard_new(NULL);
This is obviously not correct as clipboard_new returns a clipboard_c* and not clipboard_c. The initial definition of the clipboard variable is what causes the compiler to throw that incomplete struct error. It simply doesn't know how big clipboard_c is, and therefore can't allocate room for it.
If we simply change the to be:
type ClipboardObj* {.importc: "clipboard_c*", header:"libclipboard/libclipboard.h".} = pointer
The problem is now fixed. Now you tell Nim that ClipboardObj should be treated as a pointer, and when converting it to C it should be converted as clipboard_c*.
So why does your code work? In fact the {.pure.} pragma you attach to it has nothing to do with it, in doesn't do anything for object types and is only used for enums. The definitions:
type
ClipboardStruct* {.importc:"clipboard_c", header: "libclipboard/libclipboard.h".} = object
ClipboardObj* = ptr ClipboardStruct
by themselves would solve the problem on their own. This is because ClipboardStruct is now clipboard_c and ClipboardObj being defined as a pointer to that struct is now clipboard_c* which is the correct type.
While debugging this wrapping I simply looked at what Futhark generated for these bindings. With:
import futhark
importc:
absPath "/usr/lib/clang/13.0.0/include"
absPath "/usr/include"
"libclipboard.h"
var clipboard = clipboard_new(nil)
echo clipboard == nil
echo clipboard.clipboardSetText("Hello world".cstring)
clipboard.clipboardFree()
I was able to look into the generated Nim file in the cache and peek at all the definitions. If you don't want to use Futhark yourself for wrapping I can still recommend it as a learning tool. If you're able to wrap things with Futhark (and you should be able to wrap most things entirely automatically) you should be able to just have a look at the definitions there and copy them into your own code.
Thanks a lot for taking the time and for the super-detailed explanation.
What I have been doing so far is write something in Nim, got to the nimcache folder, see what code it produced, go back to Nim and so forth and so on.
And yes, I still hadn't found what this .pure was supposed to be doing... Thanks again for pointing it out. As I said before, it would be more than great if all of the pragmas where in a cheatsheet along with a brief explanation of what they are used for (and yes, the code they produce would be also great) - I guess I'll do it myself, when/if I find some free time.
Regarding Futhark, I had seen it at some point, but I guess I totally forgot about it. Perhaps, time to have another look. Good job!
Thanks again! :)