I am trying to port fmusdk for learning purposes. I already have something that compiles, now need to actually make it work.
Coming from python and not programming very often in Nim, I tend to struggle with memory related stuff.
My first question goes as follows:
The fmusdk works on an instance of the following object:
type
ModelInstance* = object
r*: ptr UncheckedArray[fmi2Real]#(NUMBER_OF_REALS)
i*: ptr UncheckedArray[fmi2Integer]#(NUMBER_OF_INTEGERS)
b*: ptr UncheckedArray[fmi2Boolean]#(NUMBER_OF_BOOLEANS)
s*: ptr UncheckedArray[fmi2String] #(NUMBER_OF_STRINGS)
isPositive*: ptr UncheckedArray[fmi2Boolean]#(NUMBER_OF_EVENT_INDICATORS)
time*: fmi2Real
instanceName*: fmi2String
`type`*: fmi2Type
GUID*: fmi2String
functions*: ptr fmi2CallbackFunctions
loggingOn*: fmi2Boolean
logCategories*: array[0..NUMBER_OF_CATEGORIES-1, fmi2Boolean]
componentEnvironment*: fmi2ComponentEnvironment
state*: ModelState
eventInfo*: fmi2EventInfo
isDirtyValues*: fmi2Boolean
isNewEventIteration*: fmi2Boolean
which contains a number of varaibles and functions.
The first function that get called is fmi2Instantiate. This function returns a pointer to the object in C. I am returning:
return unsafeAddr( comp )
> I understand that the data get garbage collected because the following function shows garbage when accessing the object fields.
I have tried return addr(comp) (no success). Now (not in github) I am trying return comp with the following signature:
proc fmi2Instantiate*( instanceName: fmi2String, fmuType: fmi2Type,
fmuGUID: fmi2String, fmuResourceLocation: fmi2String,
functions: ptr fmi2CallbackFunctions, visible: fmi2Boolean,
loggingOn: fmi2Boolean): ModelInstance =
So I am a bit lost.
Bearing in mind that then I apply {.exportc:"$1", cdecl, dynlib.} to all these functions. What would be the rigtht way of passing the instance that is created in fmi2Instantiate so that others functions can operate on its values.
Right now I was using in other functions something like this: comp: var ModelInstance
proc fmi2SetupExperiment*(comp: var ModelInstance; toleranceDefined: fmi2Boolean;
tolerance: fmi2Real; startTime: fmi2Real;
stopTimeDefined: fmi2Boolean; stopTime: fmi2Real): fmi2Status =
Thanks for you support
It looks like I had to define my model instance like:
type
ModelInstance* = ref object
....
Then just in the procs comp:ModelInstance:
proc fmi2SetupExperiment*(comp: ModelInstance; toleranceDefined: fmi2Boolean;
tolerance: fmi2Real; startTime: fmi2Real;
stopTimeDefined: fmi2Boolean; stopTime: fmi2Real): fmi2Status =
seems to keep the same address that is what I was looking for.Your task seems to be really hard.
return unsafeAddr( comp )
That way you would return the address of a stack allocated variable. That does not work. And ref objects should be always allocated my Nim, not by C libs. You may read a good C book, or maybe hire Mr. Shashlic from IRC, I think he has created tools called toast, interop and such.
Funny enough, I got inspired by this by an excellent writer! ;oP
I managed to get working a minimal example. So I feel extremely happy. I still have some doubts. And there is lot of work (fun) ahead.
One of the doubts that I have is that I am using an object managed by Nim. From all the functions exported to C to meet the FMI api, there is one fmi2FreeInstance. In C that function deallocates the memory. In Nim, I am just discarding:
proc fmi2FreeInstance(comp:ModelInstance) =
echo "ENTERING: fmi2FreeInstance"
#var comp: ptr ModelInstance = cast[ptr ModelInstance](c)
if comp.isNil:
return
if (invalidState(comp, "fmi2FreeInstance", MASK_fmi2FreeInstance)):
return
filteredLog(comp, fmi2OK, LOG_FMI_CALL, "fmi2FreeInstance")
#[
if not (comp.r.isNil):
comp.functions.freeMemory(comp.r)
#if not (comp.i.isNil):
# comp.functions.freeMemory(comp.i)
if not (comp.b.isNil):
comp.functions.freeMemory(comp.b)
if not (comp.s.isNil):
#var i:int
#for i in 0 ..< NUMBER_OF_STRINGS:
# if (comp.s[i]):
# comp.functions.freeMemory( comp.s[i] )
comp.functions.freeMemory( comp.s )
]#
#[
if (comp.isPositive):
comp.functions.freeMemory(comp.isPositive)
if (comp.instanceName):
comp.functions.freeMemory(comp.instanceName)
if (comp.GUID):
comp.functions.freeMemory( comp.GUID )
]#
#comp.functions.freeMemory(unsafeAddr(comp))
discard
I am assuming that the GC will deal with it. Is this a good assumption? Assuming you're not using the GC manually then Nim will only deal with object allocated by Nim; typically ref object that you have initialized with new.
So if you allocate a reference in Nim and free the memory in C you will have a problem. That's why memory allocated / deallocated by C is attached to ptr object type so there's no need of the GC.
I think I am in the first situation:
type
ModelInstance* = ref object
r*: seq[fmi2Real]
i*: array[0..0, fmi2Integer]
b*: seq[fmi2Boolean]
s*: seq[fmi2String]
isPositive*: seq[fmi2Boolean]
time*: fmi2Real
...
and then I create the instance like this:
var comp = ModelInstance( time:0, instanceName:instanceName, `type`:fmuType, GUID: fmuGUID )
that according the manual is implicity calling new. Funny enough, I got inspired by this
But I did not explain how to create wrappers for C libs, and I think I never will as it can be too hard.
I am assuming that the GC will deal with it. Is this a good assumption?
As I wrote above, the Nim GC handles only refs, not pointers. That is the reason why we use ptr for low level C lib wrappers. And when we use ptr data types, then we can manually free the allocated objects in the same way as we did in C. Next step is a high level wrapper that automatically frees the object. There are various strategies, gintro uses Nim proxy objects, destructors, finalizers and the gobject toggle_references.
You should really know C well when you will try it yourself. I think Mr. Shaslic from IRC works on C wrappers for many years already.
One tip for testing: Use --gc:arc. Because with default refc GC the GC frees memory delayed, so that the crash can occur really late. I used GC_Fullcollec() with default refc GC in early days to force a memory collection, but now I test generally with --gc:arc.
To expand on the excellent advice of @Stefan_Salewski :
# Create a reference. The memory allocated belongs to Nim and the GC
var comp = ModelInstance( time:0, instanceName:instanceName, `type`:fmuType, GUID: fmuGUID )
# Some stuff happens ...
# Calling a C function that free memory that belongs to the GC
omp.functions.freeMemory()
# Force a GC Collection - not necessary with ARC / ORC
GC_FullCollect() # => Memory was already free'd by C and you free it again. This is undefined behaviour.
Thanks for the suggestion. It is working with --gc:arc.
Just something to point out is that, this started as a C wrapper, but it is no longer a C wrapper. This is a port. It creates a library that fulfills a particular C api.
Just something to point out is that, this started as a C wrapper, but it is no longer a C wrapper. This is a port. It creates a library that fulfills a particular C api.
Ah I misunderstood then. With gc:arc or gc:orc you can use =destroy to implement destructor (this allows you to execute a specific callback when your object gets destroyed)