Hi,
I got a weird problem. Given the following type definitions:
type
CObject {.importc: "struct abc".} = object
CObjectPtr* = ref CObject
NimObject* = object
obj: CObjectPtr
and the following constructor:
proc newNimObject(): NimObject =
var obj: CObjectPtr
instantiateObject(addr obj)
return NimObject(obj: obj)
This can compile. However, when I tweak the type definitions into the followings:
type
CObject {.importc: "struct abc".} = object
CObjectPtr* = ref CObject
NimObjectInternal = object
obj: CObjectPtr
NimObject* = ref NimObjectInternal
With the same constructor, this does not compile. It returns the following error:
error: invalid application of 'sizeof' to incomplete type 'struct abc'
NTI__Ngsm3a2ILgQdLTgvTVDJAA_.size = sizeof(struct abc);
Any idea why?
Thanks.
Henry
We do not know what instantiateObject() really does, so we may only guess. And I am really bad at guessing :-)
type
CObject {.importc: "struct abc".} = object
CObjectPtr* = ref CObject
looks really not nice, as you have a reference to a C object. Note that references should always be created only by Nim, never by a C alloc().
And why
instantiateObject(addr obj)
to instanciate a Nim reference. Generally we would write something like
obj = newStrangeObject()
Thanks for the reply.
instantiateObject is a wrapped C function, and hence the syntax. It instantiates the C object.
I am trying to wrap a C database driver. I first wrapped the essential C structs and functions. Then I try to hold the reference to the C database connection inside a Nim object. Now, I do not want this Nim object to be shallow copied and hence I try to turn it into a ref.
Somehow when Nim object is not a ref, it works just fine. I can run it against the database, etc. The problem is that the Nim object also holds some meta data. When it is passed around and shallow-copied, the meta data are copied and not synced. Hence, I try to make the Nim object into a ref. But when I turn it into a ref, it just blows up all over the place. I have no idea why.
When I delete the constructor, it can compile. So I suspect it has something to do with the constructor code as well. :/
instantiateObject is a wrapped C function
That was my guess, and that can not work: I told you that Nim references can only be created my Nim, not by C functions. References are managed pointers, only Nim knows how to created them, and how to destroy them.
When you test your code you should try to use --gc:arc to get the crash immediately and not with a long delay. Or when you use default --gc:refc put in your code a GC_Fullcollect() call to see if it will crash when GC starts.
The strange thing is that it works fine under --gc:arc. Hmm...
Changing the NimObject from ref to ptr does not help.
The summary of the problem is that I have a ptr to a C object and I would like to hold it inside a Nim object so that the user does not have to mess with the ptr stuff. In addition, the Nim Object holds some additional data regarding the state of the operations, so it is preferable that the Nim Object to be a ref. That way the Nim Object can be passed around and the data is still synced.
How can I do that?
Something like this?
type
CObject {.importc: "struct abc", bycopy.} = object
CObjectPtr* = ptr CObject
NimObjectInternal = object
obj: CObjectPtr
NimObject* = ref NimObjectInternal
proc newNimObject(): NimObject =
result = NimObject(
obj: create(CObject) # allocates CObject on heap and returns a ptr
)
And yes, you have to use create or alloc directly but not how you did it in your example, since in your newNimObject you allocate CObject on the stack and when the proc returns the pointer will point to an invalid location.
Actually, just
type
# you'd usually want bycopy for importc'd C structs
CObject {.importc: "struct abc", bycopy.} = object
NimObjectInternal = object
obj: CObject
NimObject* = ref NimObjectInternal
proc newNimObject(): NimObject =
result = NimObject(
obj: CObject()
)
should work, since your NimObject is already a ref so you'll be able to get addr of obj when you need to
Oh, noticed that you want instantiateCObject, then something like this:
type
# you'd usually want bycopy for importc'd C structs
CObject {.importc: "struct abc", bycopy.} = object
CObjectPtr = ptr CObject
NimObjectInternal = object
obj: CObject
NimObject* = ref NimObjectInternal
proc instantiateObject(x: CObjectPtr) =
discard
proc newNimObject(): NimObject =
result = NimObject()
instantiateObject(addr result.obj)
In my code, the C object is instantiated and allocated by a C function, which I happen to wrap as well. So the code looks like this.
type
# you'd usually want bycopy for importc'd C structs
CObject {.importc: "struct abc", bycopy.} = object
CObjectPtr* = ptr CObject
NimObjectInternal = object
obj: CObjectPtr
NimObject* = ref NimObjectInternal
proc newNimObject(): NimObject =
var obj: CObjectPtr
instantiateCObject(addr obj)
result = NimObject(
obj: obj
)
Is it not the same? I think it may be easier if I am to post the actual code. Here we go.
{.push dynlib: path.}
type
Db {.importc: "struct sqlite3", bycopy.} = object
DbPtr* = ptr Db
## DbPtr represents a pointer to the database connection
proc sqlite3_open*(connectionString: cstring, db: ptr DbPtr): cint {.importc.}
## sqlite3_open opens a database connection.
proc sqlite3_close*(db: DbPtr) {.importc.}
## sqlite3_close closes a database connection and deallocates it.
{.pop}
type
DatabaseType* = enum
## DatabaseType defines the supported database type.
SQLite
ConnectionInternal = object
## Connection is a database connection.
isLocked: bool
isClosed: bool
case dbType*: DatabaseType
of SQLite:
sqliteConn: sqlite.DbPtr
Connection* = ref ConnectionInternal
proc newSqliteConnection*(filename: string): Connection {.raises: [IOError].} =
## newSqliteConnection creates a new SQLite database connection.
var conn: DbPtr
if sqlite3_open(cstring(filename), addr conn) != 0:
sqlite3_close(conn)
raise newException(IOError, "failed to establish database connection")
result = Connection(dbType: SQLite, sqliteConn: conn)
and now the test code
var conn = newSqliteConnection("test.db")
if conn.dbType == SQLite:
echo "Hey"
It crashes with the following error messages:
C:\Users\Henry\nimcache\database_d\@msqlite.nim.c: In function 'library_sqliteDatInit000':
C:\Users\Henry\nimcache\database_d\@msqlite.nim.c:93:44: error: invalid application of 'sizeof' to incomplete type 'struct sqlite3'
NTI__Ngsm3a2ILgQdLTgvTVDJAA_.size = sizeof(struct sqlite3);
^~~~~~
In file included from C:\Users\Henry\nimcache\database_d\@msqlite.nim.c:11:0:
C:\Users\Henry\nimcache\database_d\@msqlite.nim.c:94:50: error: invalid application of '__alignof__' to incomplete type 'struct sqlite3'
NTI__Ngsm3a2ILgQdLTgvTVDJAA_.align = NIM_ALIGNOF(struct sqlite3);
^
C:\Users\Henry\Programs\nim-1.4.4\lib/nimbase.h:553:38: note: in definition of macro 'NIM_ALIGNOF'
# define NIM_ALIGNOF(x) __alignof__(x)
^
Error: execution of an external compiler program 'C:\Users\Henry\Programs\nim-1.4.4\dist\mingw64\bin\gcc.exe -c -w -fmax-errors=3 -mno-ms-bitfields -IC:\Users\Henry\Programs\nim-1.4.4\lib -IC:\Users\Henry\Documents\Projects\nim\library\src\library\database -o C:\Users\Henry\nimcache\database_d\@msqlite.nim.c.o C:\Users\Henry\nimcache\database_d\@msqlite.nim.c' failed with exit code: 1
Now if I am to switch gc with --gc:arc, it works fine, including running queries against the database, etc.
I am running out of ideas.
I solved the problem. Thank you for the link that you gave me.
Instead of this:
type
Db {.importc: "struct sqlite3", bycopy.} = object
It should be this:
type
Db {.pure.} = object
Now it works fine.
To answer your question, I didn't know that there was already a wrapper for sqlite. Anyhow, I have already wrapped the sqlite C stuffs and now I am working with the higher level database abstraction.
Thanks for the help.
Henry