-
Notifications
You must be signed in to change notification settings - Fork 4
[WIP] Node bindings #68
base: master
Are you sure you want to change the base?
Conversation
FWIW, I tried adding flow but it kept failing on the |
Thanks for doing this! I briefly looked into it not long after writing that issue, and got scared off by all the steps involved, so I'm very grateful. 😅 |
No problem, I don't blame you! I actually forgot about my original idea for this which was to create the node bindings as a very thin wrapper (just the .cc files) and put the idiomatic JavaScript in That way, other AltJS languages like PureScript purescript/purescript#631 (comment) can have bindings without the overhead of the JS layer. |
4479725
to
a47699f
Compare
I've added the beginning of some bindings according to the Node Addons guide. The extent of my C++ knowledge comes from there, so I'd appreciate any feedback on this so far. |
Also, I'm getting a test failure that I don't quite understand so would appreciate another set of eyes on that:
|
Additionally I'm unsure how to deal with calling |
This has become redundant given the direction that the node bindings have taken. This could be ressurected should a more modern API be desired (e.g. property getters) but it seems like a lot of overhead at the moment.
I don't know anything about Node, but maybe I can ask the right questions or be a rubber duck. I take it you're saying' |
@kastiglione when I added
In the methods I defined, I have to do this little dance:
Notice that I have to do |
Somehow the build metadata is being returned when calling |
if (buildMetadata == NULL) { | ||
args.GetReturnValue().Set(Null(isolate)); | ||
} else { | ||
args.GetReturnValue().Set(String::NewFromUtf8(isolate, buildMetadata)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this definitely copy the string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooo, good call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs say this ¯_(ツ)_/¯
Allocates a new string from UTF-8 data.
Given the This will be closer to how the C functions work anyway, and I should be able to use the shared examples I set up to test both. |
I took a stab at that and added I verified this manually in case it was something invalid coming from the arbitrary string instances. I imagine this is related to the other test failure. |
} else { | ||
const char *string = *String::Utf8Value(args[0]->ToString()); | ||
obj = new SemanticVersion(string); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel great about this but wasn't sure how else to support multiple constructors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems fine—and very JavaScripty—to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In hindsight it sort of makes sense since JavaScript classes only support a single constructor.
I've got to be overlooking some memory management here, so any help is appreciated 🙇 |
_semanticVersion = ArbiterCreateSemanticVersionFromString(string); | ||
} | ||
|
||
SemanticVersion::~SemanticVersion() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should use ArbiterFree()
here, like @kastiglione suggested, as long as you store the semantic version pointer as non-const (which is correct, since you own it), and explicitly cast here with static_cast<void *>(…)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-const
was what I was missing when I tried this earlier.
I did this in 8d7960d but didn't need to cast.
@jspahrsummers – do you know if that would be caught at compile time if it's an issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be caught, yeah. I'm surprised that it's not an issue, since C++ doesn't have implicit casting to void *
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe it just doesn't have implicit casting from void *
…
void SemanticVersion::Init(Isolate* isolate) { | ||
// Prepare constructor template | ||
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New); | ||
tpl->SetClassName(String::NewFromUtf8(isolate, "SemanticVersion")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this sort of stuff have to be defined in C++, or is it possible to write more of it in JS? (It'd be nice if as much was JS as possible, since that's what users will see.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see—this is referring to SemanticVersion::New
, not some magic symbol. In that case, nevermind, since there isn't much which avoids the C++ class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general I wish there was a way to do more of this in JS, but I don't think there is.
This is definitely not what I thought I was getting in for in the beginning 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related: I've been going back and forth on whether it's worth providing ES6 classes that have property getters (think computed properties in Swift).
That way things like version.major
would work instead of version.getMajorversion()
, but I'm not sure if the overhead outweighs the benefit:
- we'd need to subclass or create thin wrappers around everything exported from the native bindings, which may introduce more potential for bugs
- we may need to transpile to ES5 if wanting to target older versions of Node
That was the motiviation behind 9a46594.
I think it could be a nicer API so will give it another shot once these bugs are worked out.
void SemanticVersion::Create(const FunctionCallbackInfo<Value>& args) { | ||
Isolate *isolate = args.GetIsolate(); | ||
const unsigned argc = 5; | ||
Local<Value> argv[argc] = { args[0], args[1], args[2], args[3], args[4] }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens on out-of-bounds here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it just blindly accesses invalid memory (classic C[++] style). That might explain the test failure you see.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens on out-of-bounds here?
Nothing right now, I'll address that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried this but it didn't make any difference:
View diff
- Local<Value> argv[argc] = { args[0], args[1], args[2], args[3], args[4] };
+
+ Local<Value> argv[argc] = {
+ args[0],
+ args[1],
+ args[2],
+ (args[3]->IsNull() || args[3]->IsUndefined() ? (Local<Value>)Null(isolate) : args[3]),
+ (args[4]->IsNull() || args[4]->IsUndefined() ? (Local<Value>)Null(isolate) : args[4])
+ };
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was an old diff.
View diff
- Local<Value> argv[argc] = { args[0], args[1], args[2], args[3], args[4] };
+
+ Local<Value> argv[argc] = {
+ args[0],
+ args[1],
+ args[2],
+ (args.Length() > 3 && (args[3]->IsNull() || args[3]->IsUndefined()) ? (Local<Value>)Null(isolate) : args[3]),
+ (args.Length() > 4 && (args[4]->IsNull() || args[4]->IsUndefined()) ? (Local<Value>)Null(isolate) : args[4])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That logic is wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Local<Value> argv[argc] = { args[0], args[1], args[2], args[3], args[4] };
+
+ Local<Value> argv[argc] = {
+ args[0],
+ args[1],
+ args[2],
+ (args.Length() <= 3 || args[3]->IsNull() || args[3]->IsUndefined() ? (Local<Value>)Null(isolate) : args[3]),
+ (args.Length() <= 4 || args[4]->IsNull() || args[4]->IsUndefined() ? (Local<Value>)Null(isolate) : args[4])
I'm going to take another stab at this soon but welcome any and all ideas in the meantime 😄 |
This relates to #33. Just getting things set up using the info from https://nodejs.org/api/addons.html
This creates a Node.js Addon in C++ with a function that gets re-exported by ES6 JavaScript and then tested using Jest.