[Vm-dev] Slang newbie question

David T. Lewis lewis at mail.msen.com
Mon Sep 14 01:56:29 UTC 2009


On Sun, Sep 13, 2009 at 01:23:32PM -0700, Ronald Spengler wrote:
>  
> Hello everyone.
> I have a named primitive, and I need to send a ByteString to it, to be
> processed and returned by an external library. To get a string into Slang,
> should I send it #asByteArray, and would that let me treat the bytes as
> integers on the stack? I'm basically trying to get a char* on the other
> side.

You can use the ByteString as a parameter to the primitive, no problem.
The only tricky bit is that C expects null terminated strings, so you need
to copy the contents of the ByteString into a null terminated array before
you can let it be used by the C library as a char *.

I'm sure there are lots of examples, but you can look at
OSProcessPlugin>>cStringFromString: and OSProcess>>transientCStringFromString:
for examples of how to copy the string buffer into a null terminated buffer
for use in C. Look at senders of these two methods for examples of primitives
that pass strings as parameters. (OSProcessPlugin is on SqueakSource if you
do not have it).

Following are a couple of examples taken from OSPP. In both cases, a buffer
is allocated with size one greater than the string length, and the contents
of the Smalltalk string are copied into the buffer space with a trailing
null terminator. The #primitiveChdir example allocates a new Smalltalk
string to use for the buffer, and #primitivePutEnv uses malloc to allocate
the new buffer (because in this case the buffer must be "permanently" valid
after the primitive exits).

primitiveChdir
	"Call chdir(2) to change current working directory to the specified path string. Answer
	nil for success, or errno on failure."

	| path errno |
	self export: true.
	self var: 'path' type: 'char *'.
	self var: 'errno' type: 'extern int'.
	path := self transientCStringFromString: (interpreterProxy stackObjectValue: 0).
	(self chdir: path)
		ifTrue: [interpreterProxy pop: 2; push: interpreterProxy nilObject]
		ifFalse: [interpreterProxy pop: 2; pushInteger: errno].

transientCStringFromString: aString
	"Answer a new null-terminated C string copied from aString.
	The string is allocated in object memory, and will be moved
	without warning by the garbage collector. Any C pointer
	reference the the result is valid only until the garbage
	collector next runs. Therefore, this method should only be used
	within a single primitive in a section of code in which the
	garbage collector is guaranteed not to run. Note also that
	this method may itself invoke the garbage collector prior
	to allocating the new C string.

	Warning: The result of this method will be invalidated by the
	next garbage collection, including a GC triggered by creation
	of a new object within a primitive. Do not call this method
	twice to obtain two string pointers."

	| len stringPtr newString cString |
	self returnTypeC: 'char *'.
	self var: 'stringPtr' declareC: 'char *stringPtr'.
	self var: 'cString' declareC: 'char *cString'.
	len := interpreterProxy sizeOfSTArrayFromCPrimitive: (interpreterProxy arrayValueOf: aString).
	"Allocate space for a null terminated C string."
	interpreterProxy pushRemappableOop: aString.
	newString := interpreterProxy
		instantiateClass: interpreterProxy classString
		indexableSize: len + 1.
	stringPtr := interpreterProxy arrayValueOf: interpreterProxy popRemappableOop.
	cString := interpreterProxy arrayValueOf: newString.		"Point to the actual C string."
	self cCode: '(char *)strncpy(cString, stringPtr, len)'.		"Make a copy of the string."
	cString at: (len) put: 0.					"Null terminate the C string."
	^ cString

primitivePutEnv
	"Set an environment variable using a string of the form 'KEY=value'. This
	implementation allocates a C string using malloc to allocate from the C heap
	(using cStringFromString rather than transientCStringFromString). This
	is necessary because the C runtime library does not make a copy of the
	string into separately allocated environment memory."

	| cStringPtr keyValueString |
	self export: true.
	self var: 'cStringPtr' declareC: 'char *cStringPtr'.
	keyValueString := interpreterProxy stackObjectValue: 0.
	cStringPtr := self cStringFromString: keyValueString.
	((self putenv: cStringPtr) == 0)	"Set environment variable."
		ifTrue: [interpreterProxy pop: 2; push: keyValueString]
		ifFalse: [^ interpreterProxy primitiveFail]

cStringFromString: aString
	"Answer a new null-terminated C string copied from aString. The C string
	is allocated from the C runtime heap. See transientCStringFromString for
	a version which allocates from object memory.
	Caution: This may invoke the garbage collector."

	| len sPtr cString |
	self returnTypeC: 'char *'.
	self var: 'sPtr' declareC: 'char *sPtr'.
	self var: 'cString' declareC: 'char *cString'.
	sPtr := interpreterProxy arrayValueOf: aString.
	len := interpreterProxy sizeOfSTArrayFromCPrimitive: sPtr.
	cString := self callocWrapper: len + 1 size: 1.		"Space for a null terminated C string."
	self cCode: '(char *) strncpy (cString, sPtr, len)'.	"Copy the string."
	^ cString

callocWrapper: count size: objectSize
	"Using malloc() and calloc() is something I would like to avoid, since it is
	likely to cause problems some time in the future if somebody redesigns
	object memory allocation. This wrapper just makes it easy to find senders
	of calloc() in my code. -dtl"

	self returnTypeC: 'void *'.
	^ self cCode: 'calloc(count, objectSize)'

Dave



More information about the Vm-dev mailing list