https://kotlinlang.org logo
Title
n

napperley

04/01/2018, 6:59 AM
Is there a way to manually allocate memory for reference types (eg String)? Both the
alloc
and
allocArray
functions only handle primitive types.
o

olonho

04/01/2018, 1:47 PM
it seems, there’s some confusion between Kotlin object memory management and memory allocated for external world and accessible via interop. It is very unlikely, that you actually need custom placement for Kotlin objects such as kotlin.String. Typically, when debugging interoperability memory issues, existing memory debugging tools for native applications, such as valgrind could be very helpful.
for example for your case valgrind says:
==15941== Invalid read of size 8
==15941==    at 0x5F8CFFF: _nc_Connect_Items (in /usr/lib/x86_64-linux-gnu/libmenu.so.5.9)
==15941==    by 0x5F8E462: new_menu (in /usr/lib/x86_64-linux-gnu/libmenu.so.5.9)
==15941==    by 0x436E24: ncurses_kniBridge336 (in /home/nike/kotlin-native/curses.kexe)
==15941==    by 0x436776: kfun:ncurses.new_menu(kotlinx.cinterop.CValuesRef<kotlinx.cinterop.CPointerVarOf<kotlinx.cinterop.CPointer<ncurses.tagITEM>>>?)ValueType (in /home/nike/kotlin-native/curses.kexe)
==15941==    by 0x4049C1: kfun:main(kotlin.Array<kotlin.String>) (curses.kt:32)
==15941==    by 0x404745: EntryPointSelector (in /home/nike/kotlin-native/curses.kexe)
==15941==    by 0x4046B6: Konan_start (start.kt:29)
==15941==    by 0x404631: Konan_run_start (curses.kt:19)
==15941==    by 0x4045A6: Konan_main (curses.kt:19)
==15941==    by 0x5796F44: (below main) (libc-start.c:287)
==15941==  Address 0x61c5410 is 0 bytes after a block of size 48 alloc'd
==15941==    at 0x4C2CC70: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==15941==    by 0x433952: Kotlin_interop_malloc (in /home/nike/kotlin-native/curses.kexe)
==15941==    by 0x413B0E: kfun:kotlinx.cinterop.nativeMemUtils.alloc(kotlin.Long;kotlin.Int)ValueType (NativeMem.kt:103)
==15941==    by 0x4139CC: kfun:kotlinx.cinterop.nativeHeap.alloc(kotlin.Long;kotlin.Int)ValueType (Utils.kt:34)
==15941==    by 0x413E69: kfun:kotlinx.cinterop.ArenaBase.alloc(kotlin.Long;kotlin.Int)ValueType (Utils.kt:80)
==15941==    by 0x413A77: kfun:kotlinx.cinterop.NativePlacement.alloc(<http://kotlin.Int;kotlin.Int|kotlin.Int;kotlin.Int>)ValueType (in /home/nike/kotlin-native/curses.kexe)
==15941==    by 0x4182E4: kfun:kotlinx.cinterop.placeBytes#internal (Utils.kt:229)
==15941==    by 0x4181B9: kfun:kotlinx.cinterop.object-2.getPointer#internal (Utils.kt:239)
==15941==    by 0x436762: kfun:ncurses.new_menu(kotlinx.cinterop.CValuesRef<kotlinx.cinterop.CPointerVarOf<kotlinx.cinterop.CPointer<ncurses.tagITEM>>>?)ValueType (in /home/nike/kotlin-native/curses.kexe)
==15941==    by 0x4049C1: kfun:main(kotlin.Array<kotlin.String>) (curses.kt:32)
==15941==    by 0x404745: EntryPointSelector (in /home/nike/kotlin-native/curses.kexe)
==15941==    by 0x4046B6: Konan_start (start.kt:29)
and
man new_menu
clearly states that
The  function  new_menu  creates a new menu connected to a specified item pointer array (which
       must be NULL-terminated).
your last problem is the harder one, although, and related to design of curses API. Essentially you need to pass a C string, which is valid for longer time that just API call itself. So somewhat kludgy approach is to avoid K/N internal string conversion, and manage the C pointers memory yourself. See snippet below.
👍 1
import kotlinx.cinterop.*

import ncurses.*

import kotlin.system.exitProcess

val exitKeys = setOf('e'.toInt(), 'E'.toInt())
val choices = arrayOf(
	"Choice 1",
	"Choice 2",
	"Choice 3",
	"Choice 4",
	"Exit"
)
val items = arrayOfNulls<CPointer<ITEM>?>(6)
var menu: CPointer<MENU>? = null
var itemsValues: CValuesRef<CPointerVar<ITEM>>? = null

fun main(args: Array<String>) = memScoped {
	setupScreen()
	choices.forEachIndexed { pos, c -> 
          val name = c.cstr.getPointer(this)
          items[pos] = new_item(name, name)
        }
	itemsValues = items.toCValues().getPointer(this)
	menu = new_menu(itemsValues)

	if (menu == null) {
		mvprintw(1, 0, "Error: Cannot create menu.")
		exitProcess(-1)
	} else {
		mvprintw(1, 0, "Menu created.")
	}
	mvprintw(2, 0, "Press e to Exit")
	mvprintw(3, 0, "Showing menu...\n")
	refresh()
	// Show the menu.
	post_menu(menu)

	mvprintw(3, 0, "Reading key input...\n")
	refresh()
	readKeyInput()
	cleanUp()
}

private fun setupScreen() {
	initscr()
	cbreak()
	noecho()
	keypad(stdscr, true)
}

private fun cleanUp() {
	free_menu(menu)
	items.forEach { free_item(it) }
	endwin()
}

private fun readKeyInput() {
	if (menu != null) {
		var key: Int

		do {
			// Read a character.
			key = getch()
			when (key) {
				KEY_DOWN -> menu_driver(menu, REQ_DOWN_ITEM)
				KEY_UP -> menu_driver(menu, REQ_UP_ITEM)
			}
		} while (key !in exitKeys)
	}
}
👍 1
also you need .def file like that
package = ncurses
headers = ncurses.h menu.h
headerFilter = ncurses.h menu.h
noStringConversion = new_item
n

napperley

04/01/2018, 10:20 PM
So the key differences in the def file are the addition of the package, headerFilter, and noStringConversion entries, where noStringConversion suppresses automatic String conversions for the
new_item
function (in the ncurses lib). I see that the bindings will need to be regenerated. 🙁
o

olonho

04/01/2018, 10:48 PM
key one is
noStringConversion
everything else you likely already had in place
didn't you produce ncurses bindings yourself already? don't think they are in platform libs
n

napperley

04/01/2018, 10:51 PM
Not a big issue with regenerating the bindings, appears to be quicker generating them the 2nd time round. 😄