Hi, it’s been a while since I’ve tested this. I integrated hashlink into my engine sometime last year, but ended up not using it for my current project. So I haven’t been keeping it up to date with changes to HL, but here’s a simple interface I wrote to work with HLC (not the VM) from C++. I haven’t tested the VM, but I suspect that the interface isn’t too dissimilar.
//HLI.h
#ifndef __HLI_H__
#ifdef USE_HASHLINK
#define __HLI_H__
#include "inttypes.h"
extern "C" {
#include <hlc.h>
#include <_std/String.h>
}
namespace hli {
class HLI {
public:
HLI();
~HLI();
void initialize();
/**
Will allocate a new instance of the given
type and call its constructor
*/
vdynamic *createInstance(hl_type *type, ...);
hl_field_lookup *resolveField(vdynamic *inst, const char *fname);
hl_field_lookup *resolveField(vdynamic *inst, const uchar *fname);
vdynamic *callFunction(vdynamic *obj, hl_field_lookup *field, ...);
vdynamic *callFunction(vdynamic *obj, const char *fname, ...);
vdynamic *callFunction(vdynamic *obj, const uchar *fname, ...);
vdynamic *callFunction(vdynamic *obj, hl_field_lookup *field, va_list &argptr);
/**
Use these functions to pass values to Haxe functions
when using `callFunction`
*/
static String allocString(const char *str);
static String allocString(const uchar *str);
static String allocStringf(const char *str, ...);
static String allocStringf(const uchar *str, ...);
static vdynamic *allocInt32(int32_t value);
static vdynamic *allocInt64(int64_t value);
static vdynamic *allocBool(bool value);
static vdynamic *allocFloat32(float value);
static vdynamic *allocFloat64(double value);
};
}
#endif
#endif
and
//HLI.cpp
#ifdef USE_HASHLINK
#include "hli/HLI.h"
#include "Log.h"
LOG_TAG("HLI")
#include <EASTL/string.h>
#include <EAStdC/EASprintf.h>
extern "C" {
#include <hl.h>
#include <hlc.h>
#include <hl/natives.h>
#include <_std/String.h>
extern hl_type t$String;
}
using namespace hli;
#define HL_MAX_ARGS 9
#define HL_MAX_FIELD_LEN 128
HLI::HLI(){
Logi("HLI::construct");
}
HLI::~HLI(){
Logi("HLI::destroy");
}
void HLI::initialize(){
Logi("HLI::initialize");
}
static hl_field_lookup *obj_resolve_field(hl_type_obj *o, int hfield) {
hl_runtime_obj *rt = o->rt;
do {
hl_field_lookup *f = hl_lookup_find(rt->lookup,rt->nlookup,hfield);
if( f ) return f;
rt = rt->parent;
} while( rt );
return NULL;
}
hl_field_lookup *HLI::resolveField(vdynamic *inst, const char *fname){
int len = strlen(fname);
uchar ucs2[HL_MAX_FIELD_LEN];
if(len > HL_MAX_FIELD_LEN) len = HL_MAX_FIELD_LEN;
asciiToUCS2(&fname[0], len, &ucs2[0]);
return resolveField(inst, ucs2);
}
hl_field_lookup *HLI::resolveField(vdynamic *inst, const uchar *fname){
int hash = hl_hash((vbyte*) fname);
return obj_resolve_field(inst->t->obj, hash);
}
//https://github.com/HaxeFoundation/hashlink/issues/253
static vdynamic *hl_dyn_call_constructor(vclosure *c, vdynamic *cons, vdynamic **args, int nargs){
struct {
varray a;
vdynamic *args[HL_MAX_ARGS+1];
} tmp;
vclosure ctmp;
int i = 0;
if( nargs > HL_MAX_ARGS ) hl_error("Too many arguments");
tmp.a.t = &hlt_array;
tmp.a.at = &hlt_dyn;
tmp.a.size = nargs;
if( c->hasValue && c->t->fun->nargs >= 0 ) {
ctmp.t = c->t->fun->parent;
ctmp.hasValue = 0;
ctmp.fun = c->fun;
//tmp.args[0] = hl_make_dyn(&c->value,ctmp.t->fun->args[0]);
tmp.args[0] = cons;
tmp.a.size++;
for(i=0;i<nargs;i++){
tmp.args[i+1] = args[i];
}
c = &ctmp;
} else {
for(i=0;i<nargs;i++){
tmp.args[i] = args[i];
}
}
return hl_call_method((vdynamic*)c,&tmp.a);
}
vdynamic *HLI::createInstance(hl_type *type, ...){
//Allocate new instance
vdynamic* instMain = hl_alloc_obj(type);
if(instMain == nullptr){
Loge("Failed to allocate instance");
return NULL;
}
//TODO verify if this is necessary, or cache global instances
vdynamic* glbl = *(vdynamic**)type->obj->global_value;
vdynamic* instGlbl = hl_alloc_obj(glbl->t);
if(instGlbl == nullptr){
Loge("Failed to allocate global instance");
return NULL;
}
/////
int hash = hl_hash((vbyte*)USTR("__constructor__"));
if(!hl_obj_has_field(instGlbl, hash)){
Logfw("object doesn't have constructor field (0x%X)", hash);
return NULL;
}
vclosure *ctor = (vclosure*) hl_obj_get_field(instGlbl, hash);
//////////////
int nargs = ctor->t->fun->nargs;
va_list argptr;
va_start(argptr, type);
vdynamic *args[HL_MAX_ARGS+1];
for(int ai = 0; ai < nargs; ai++){
args[ai] = va_arg(argptr, vdynamic*);
}
va_end(argptr);
hl_dyn_call_constructor((vclosure*)ctor, instMain, args, nargs);
return instMain;
}
vdynamic *HLI::callFunction(vdynamic *obj, hl_field_lookup *field, ...) {
va_list argptr;
va_start(argptr, field);
vdynamic *ret = callFunction(obj, field, argptr);
va_end(argptr);
return ret;
}
vdynamic *HLI::callFunction(vdynamic *obj, const uchar *fname, ...){
hl_field_lookup *f = resolveField(obj, fname);
va_list argptr;
va_start(argptr, fname);
vdynamic *ret = callFunction(obj, f, argptr);
va_end(argptr);
return ret;
}
vdynamic *HLI::callFunction(vdynamic *obj, const char *fname, ...){
hl_field_lookup *f = resolveField(obj, fname);
va_list argptr;
va_start(argptr, fname);
vdynamic *ret = callFunction(obj, f, argptr);
va_end(argptr);
return ret;
}
vdynamic *HLI::callFunction(vdynamic *obj, hl_field_lookup *field, va_list &argptr) {
hl_runtime_obj *rt = obj->t->obj->rt ;
int nargs = field->t->fun->nargs - 1;
vdynamic *args[HL_MAX_ARGS + 1];
for(int i=0; i<nargs; i++){
args[i] = va_arg(argptr, vdynamic*);
}
vclosure *ctmp = hl_alloc_closure_ptr(
field->t,
rt->methods[-field->field_index-1],
obj
);
return hl_dyn_call(ctmp, args, nargs);
}
String HLI::allocString(const char *str){
hl_buffer *b = hl_alloc_buffer();
String ret = (String)hl_alloc_obj(&t$String);
int len;
hl_buffer_cstr(b, str);
ret->bytes = (vbyte*) hl_buffer_content(b, &len);
ret->length = len;
return ret;
}
String HLI::allocString(const uchar *str){
hl_buffer *b = hl_alloc_buffer();
String ret = (String)hl_alloc_obj(&t$String);
int len;
hl_buffer_str(b, (uchar*) str);
ret->bytes = (vbyte*) hl_buffer_content(b, &len);
ret->length = len;
return ret;
}
String HLI::allocStringf(const char *str, ...){
va_list argptr;
va_start(argptr, str);
eastl::basic_string<char> ret;
ret.append_sprintf_va_list(str, argptr);
va_end(argptr);
return allocString(&ret[0]);
}
String HLI::allocStringf(const uchar *str, ...){
va_list argptr;
va_start(argptr, str);
eastl::basic_string<uchar> ret;
ret.append_sprintf_va_list(str, argptr);
va_end(argptr);
//return allocString((uchar*)&fstr[0]);
return allocString((uchar*)&ret[0]);
}
vdynamic *HLI::allocInt32(int32_t value){
return hl_make_dyn(&value, &hlt_i32);
}
vdynamic *HLI::allocInt64(int64_t value){
return hl_make_dyn(&value, &hlt_i64);
}
vdynamic *HLI::allocBool(bool value){
return hl_make_dyn(&value, &hlt_bool);
}
vdynamic *HLI::allocFloat32(float value){
return hl_make_dyn(&value, &hlt_f32);
}
vdynamic *HLI::allocFloat64(double value){
return hl_make_dyn(&value, &hlt_f64);
}
#endif
If you want to drop that into your project, you’ll have to remove my logging code, and replace the EASTL templates with regular STL ones (unless you’re using EASTL). Also, I do remember making some changes to the HL source code while porting to PS Vita (homebrew), and maybe some stuff with UCS2 strings. So those changes combined with potential breaking changes in the latest HL (like the new GC) might make this obsolete, but maybe it’ll help you anyways.
For what it’s worth though, this code did work at some point, even on a Vita via homebrew