====== NervLuna: generating bindings for nvCore - second run ======
{{tag>cpp dev nvluna bindings}}
So here we are again, with yet another dev session to try to get the lua bindings for my nvCore module to finally compile properly. On the table today: abstract classes, unnamed arguments, already defined functions and finally link errors: "Bon appétit!" LOL. But hey, the issues are not going to simply automatically fix themself, so no choice here: we have to get to work ;-)!
====== ======
===== No construction for abstract class =====
As discussed in [[blog:2020:0618_nervluna_operators_handling|my previous article]] the next issue on the list is on the generation of a constructor for abstract class. It turns out this is relatively easy to fix: from libclang we have the function **clang_CXXRecord_isAbstract** which can be used to figure out if a given class/struct cursor is abstract, so I just need to extend a bit the mechanism already in place.
I add in my "Class" class:
function Class:isAbstract()
return self._isAbstract
end
function Class:setAbstract(val)
if val then
logDEBUG("Detected abstract class: ", self:getFullName())
end
self._isAbstract = val
end
Then I update that "abstract" flag when parsing a given class. And finally, if I notice a class is abstract when writing its binding file, I just pretend we don't have any public constructor available (and that will do the trick: users won't be able to contruct that class from lua and the generated code will compile):
local cons = cl:getConstructor()
local pubCtorSigs = cons and cons:getPublicSignatures(true) or {}
if cl:isAbstract() then
logDEBUG("Disallowing construction of abstract class ", fname)
pubCtorSigs = {}
end
And finally we also need to prevent the generation of the contructor function binding at the FunctionWriter level with:
elseif f:isConstructor() and f:getParent():isAbstract() then
-- Should not provide any bindings for abstract classes constructor:
logDEBUG("Ignoring abstract class constructor: ", fname)
self:writeLine("// NervLuna: Ignoring abstract class constructor '%s'", fname)
===== Unnamed arguments issue =====
With the abstract class issue out of the way, the next compilation error I face is in the bindings generated for the matrice classes I have:
// Bind for makeScale (1) with signature: void (const nv::Vec3f &)
static int _bind_makeScale_sig1(lua_State* L) {
// When reaching this call, we assume that the type checking is already done.
nv::Matrixd* self = Luna< nv::Matrixd >::get(L,1);
ASSERT(self!=nullptr);
nv::Vec3f* = Luna< nv::Vec3f >::get(L,2,false);
self->makeScale(*);
return 0;
}
And well, that's the thing: there is just no name provided for the arguments in that case, since we have the functions declaration:
void makeScale( const nv::Vec3f& );
void makeScale( const Vec3d& );
void makeScale( value_type, value_type, value_type );
void makeTranslate( const nv::Vec3f& );
void makeTranslate( const Vec3d& );
void makeTranslate( value_type, value_type, value_type );
=> So, when parsing the function arguments, we should provide default names in case none is provided:
for i=0,num-1 do
local arg = cursor:getArgument(i)
local argname = arg:getSpelling()
-- #112: use a default argument name if none is provided.
if argname == "" then
argname = "arg"..i
end
logDEBUG("Arg ",i," cursor: ", argname,", type: ", arg:getType():getSpelling())
-- For each argument we assign the current cursor context:
self:setContextCursor(paramDecls[i+1])
sig:addArgument(self:resolveArgument(arg))
end
And now the results are much better :-) :
// Bind for makeScale (1) with signature: void (const nv::Vec3f &)
static int _bind_makeScale_sig1(lua_State* L) {
// When reaching this call, we assume that the type checking is already done.
nv::Matrixd* self = Luna< nv::Matrixd >::get(L,1);
ASSERT(self!=nullptr);
nv::Vec3f* arg0 = Luna< nv::Vec3f >::get(L,2,false);
self->makeScale(*arg0);
return 0;
}
Good!
===== Function already defined issue =====
And now it seems the last item on the table for this session will be with multiple definitions of the same operator in the global functions list:
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(679): error C2084: function 'bool nv::_check_op_lshift_sig1(lua_State *)' already has a body
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(28): note: see previous definition of '_check_op_lshift_sig1'
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(765): error C2065: '_check_op_lshift_sig1': undeclared identifier
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(1944): error C2084: function 'int nv::_bind_op_lshift_sig1(lua_State *)' already has a body
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(752): note: see previous definition of '_bind_op_lshift_sig1'
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(2028): error C2084: function 'int nv::_bind_op_lshift(lua_State *)' already has a body
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(764): note: see previous definition of '_bind_op_lshift'
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(2029): error C2065: '_check_op_lshift_sig1': undeclared identifier
W:\Projects\NervSeed\sources\lua_bindings\wip_Core\src\luna\register_functions.cpp(2029): error C2065: '_bind_op_lshift_sig1': undeclared identifier
Checking the generated code here is what I found:
// Typecheck for operator<< (1) with signature: std::ostream &(std::ostream &, const nv::Quat &)
static bool _check_op_lshift_sig1(lua_State* L) {
int luatop = lua_gettop(L);
if( luatop!=2 ) return false;
if( !luna_isInstanceOf(L,1,SID("std::ostream"),false) ) return false;
if( !luna_isInstanceOf(L,2,SID("nv::Quat"),false) ) return false;
return true;
}
// Typecheck for operator<< (1) with signature: std::ostream &(std::ostream &, const nv::Matrixd &)
static bool _check_op_lshift_sig1(lua_State* L) {
int luatop = lua_gettop(L);
if( luatop!=2 ) return false;
if( !luna_isInstanceOf(L,1,SID("std::ostream"),false) ) return false;
if( !luna_isInstanceOf(L,2,SID("nv::Matrixd"),false) ) return false;
return true;
}
// Typecheck for operator<< (2) with signature: std::ostream &(std::ostream &, const nv::Vec3f &)
static bool _check_op_lshift_sig2(lua_State* L) {
int luatop = lua_gettop(L);
if( luatop!=2 ) return false;
if( !luna_isInstanceOf(L,1,SID("std::ostream"),false) ) return false;
if( !luna_isInstanceOf(L,2,SID("nv::Vec3f"),false) ) return false;
return true;
}
So we have 3 different overloads for this %%operator<<%% but it seems the version generated for nv::Quat is put in a "separated bag" compared to the versions for nv::Matrixd and nv::Vec3f... How could that be ?
In my log file, it seems the function "%%operator<<%%" is created only once...
Ohhh okay :-), I see now: in fact I really have 2 different functions here: one is "%%operator<<%%", and the other is "%%std::operator<<%%"
I just noticed I wasn't defining the function inside the std namespace for the nv::Quat version: in fact I should change that, but that's not the problem here.
=> So I think the solution here would be to also specify the namespace for the functions in this case: **OK** this works!
===== Resolving link errors =====
Okay, so with our latest fix on function binding prefixes, the compilation of the objects itself is successful, but then **link** stage will fail, apparently for 2 different reasons:
* **A.** On one side, we are trying to use a luna class for void: this may sound weird, but it is actually required in our system: void must be considered as a class on its own for us to be able to retrieve void pointers correctly from the lua stack.
* **B.** On the other side, it seems we are trying to build bindings for some functions that are indeed defined but are **not** exported from the nvCore module itself. [//That part might be more tricky to handle//]
The fix for point A is simple: when done parsing the input files and just before starting to write the bindings, we manually inject a "void" class in the entity tree with this config entry:
cfg.processEntities = function(root)
-- We should register the "void" type:
local cl = root:getOrCreateClass("void")
cl:setDefined(false)
end
Concerning the point B, the offending functions in my source module seem to be the following:
namespace nv
{
bool installDebugHandlers();
bool installExitHandlers();
NVCORE_EXPORT void * allocate_block(std::size_t blockSize);
NVCORE_EXPORT void allocate_block(void * p, std::size_t blockSize);
}
And indeed... the 2 first functions are not marked for export, so we should not try to use them, and the 2 next functions are marked for export, but I just realized they are not defined anywhere in the source module! So the link error is completely legit in this case ;-).
So I just commented out the declaration of the two missing functions, yet, the case of the installXXXHandlers is somewhat more interesting: from a binding generation perspective, we could be linking to a static library for instance, and in this case, we don't need to specify anything else and the bindings generation/compilation will work. But if we are linking to a dynamic library [and we are on windows], we need to see the %%__declspec(dllexport)%% attribute to support binding to the function of interest. So, how could we handle this dual setup issue ? [//Let's think a little about it Manu....//]
**Note**: I just noticed the libclang functions **clang_getCursorLinkage** and **clang_getCursorVisibility**: maybe these can provide me with the information I need here ? Let's check that.
=> Nope :-( : unfortunately this doesn't seem to help here: all my global functions have the linkage "External" and visibility "Default", so this will not help.
Okay, so now, it seems I can in fact retrieve the "attribute(dllimport)" cursor when this is applicable from class/struct declaration cursor. But for global functions I don't see that cursor yet, let's investigate this further. Hmmm, in fact that was easy: displaying the cursor children when parsing a function cursor, I can observe that I have such an attribute (when applicable) as a direct child, for instance:
[Debug] Processing: 'msleep' kind: 'FunctionDecl
[Debug] Creating function nv::msleep
[Debug] level 1: '' of kind 'attribute(dllimport)', type: ''
[Debug] level 1: 'ms' of kind 'ParmDecl', type: 'int'
[Debug] Adding signature void (int) to function msleep
[Debug] Return type: void
[Debug] Resolving type void
=> So I should retrieve that attribute at that level.
Actually, the dllimport attribute should be specified per signature in this case. Because some signatures might be imported/exported while others are not.
OK! Now I can also tell if a function (or rather a function signature) is marked as "dll imported". Let's now try to use this information to allow/disallow the bindings generation.
So after adding support to mark signatures as "dllimported or not" I quickly realized I would also need an "isDefined" flag for each signature: because when a function is defined while parsing then it means we don't need to import it. (think about inline or any other form of header defined functions).
Yet, I'm now facing a strange issue where some of the functions I want to bind are considered non-public or non-bindable somehow (?):
[Debug] Ignoring non public/bindable class function: nv::Matrixd::ptr
[Debug] Ignoring non public/bindable class function: nv::Matrixd::getOrtho
[Debug] Ignoring non public/bindable class function: nv::Matrixd::getFrustum
[Debug] Ignoring non public/bindable class function: nv::Matrixd::getPerspective
Arrff... Stupid me ;-) => This was just a dumb regression error... but now this is fixed. And, surprise !! The lua bindings module for nvCore **is now compiling** ! Yeeppeeeee :-)!
==== Conclusion ====
So now we can certainly call this a day! We fixed a lot of significant problem during this session, and with the successful compilation of the nvCore bindings this work is finally starting to pay off, feeewww!
Of course, there is still a great lot to do: in case you don't remember, we are currently only compiling a very small subset of the nvCore module content [ie. basically, only the stuff that is provided in the main "core_common.h" header file]. I'm sure we will face more problems when we start extending the list of included header files. And even without this: many functions are currently not binded because we are ignoring them [because no proper "lua converter" is provided for the argument/return types].
But this will be for another day. For the moment, let's relax for a while and enjoy the work done so far because I need a break [//... and I don't have any kitkat left...// mouuuahh ahh ahh] [//Okay, I'm out. :-)//]