Calling haxe constructor from C/C++ with embedded hashlink

I would imagine there is an example of calling a haxe constructor from c/c++ with embedded hl but no such luck.

I’ve seen:

and:

However, I’m not sure what to make of them.
There’s similar advise on both of them but I can’t seem to get it working without an example.

Is there any concise example of how to do this somewhere?

Really wish the vm internals had better documentation.

void ezHashLinkManager::Test()
{
  for (ezInt32 i = 0; i < m_pModule->code->ntypes; i++)
  {
    hl_type* type = m_pModule->code->types+i;

    if (type->kind != HOBJ)
      continue;

    auto testClass = USTR("test2x.TestClass");
    if (ucmp(type->obj->name, testClass) == 0)
    {
      vdynamic* global = *(vdynamic**)type->obj->global_value;
      vdynamic* globalObj = hl_alloc_obj(global->t);
      vdynamic* obj = hl_alloc_obj(type);

      int ctorHash = hl_hash((vbyte*)USTR("__constructor__"));
      hl_field_lookup* ctorField = obj_resolve_field(global->t->obj, ctorHash); // hl_lookup_find(global->t->obj->rt->lookup, global->t->obj->rt->nlookup, ctorHash);
      if (ctorField)
      {
        //vclosure v;
        //v.t = globalObj->t->obj->m->functions_types[ctorField->field_index];
        //v.fun = globalObj->t->obj->m->functions_ptrs[ctorField->field_index];
        //v.hasValue = 0;
        //vdynamic* ret = nullptr;
        //hl_dyn_call_obj(globalObj, ctorField->t, ctorHash, nullptr, ret);
        //vclosure cl;
        //cl.fun = ctorField->hashed_name;
        //cl.t = ctorField->t;
        //cl.hasValue = 0;
        //vdynamic* ret = nullptr;
        //hl_dyn_call(&cl, nullptr, 0);
        //hl_dyn_call_obj(global, ctorField->t, ctorHash, nullptr, ret);
      }

    }
  }
}

I am trying various things. Once I have gotten the constructor I’m not sure how I can call it here.

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 :stuck_out_tongue:

Thanks you. I’ll give this a try and report back.

Taking your code as inspiration, I was able to call the constructor without the hack workaround you got from the github issue.

static vdynamic* create_instance(hl_type* type, ...)
{
  vdynamic* global = *(vdynamic**)type->obj->global_value;
  if (global == nullptr)
  {
    ezLog::Error("Cannot instantiate class from type.");
    return nullptr;
  }

  vdynamic* globalObj = hl_alloc_obj(global->t);
  if (globalObj == nullptr)
  {
    ezLog::Error("Failed to allocate global instance.");
    return nullptr;
  }

  vdynamic* obj = hl_alloc_obj(type);
  if (obj == nullptr)
  {
    ezLog::Error("Failed to allocate instance.");
    return nullptr;
  }

  ezInt32 ctorHash = hl_hash((vbyte*)USTR("__constructor__"));
  hl_field_lookup* ctorField = obj_resolve_field(globalObj->t->obj, ctorHash);
  if (ctorField == nullptr)
  {
    ezLog::Error("Type does not have a constructor.");
    return nullptr;
  }

  vclosure* ctorClosure = (vclosure*)hl_dyn_getp(globalObj, ctorField->hashed_name, &hlt_dyn);

  ezUInt32 nargs = (ezUInt32)ctorClosure->t->fun->nargs;

  va_list argPtr;
  va_start(argPtr, type);
  vdynamic* args[HL_MAX_ARGS + 1];
  for (ezUInt32 i = 0; i < nargs; i++)
  {
    args[i] = va_arg(argPtr, vdynamic*);
  }
  va_end(argPtr);

  ctorClosure->value = obj;
  hl_dyn_call(ctorClosure, args, nargs);

  return obj;
}

It seems to work as expected. However, I still don’t know if this is the correct way to do it as there is no documentation to confirm it.