When to Lock and Unlock?

I’m a bit confused on when to lock and unlock objects and strings. Say for example:

void func(REALobject instance, REALstring blahString)

Say I am using blahString only in this function to pass onto C. Do I lock blahString at any point? Should I unlock it before returning?

void func(REALobject instance, REALstring blahObject)

Say blahObject is a FolderItem and I am going to call NativePath on it using it only in this method. Should I lock it? Should I unlock it before returning?

Thanks for any pointers on this subject, even pointers to where to go read about it (in detail). Looking at the Plugin SDK docs, I come away confused as to when in these cases.

Xojo uses a simple lock/unlock model:

  • Values passed into your function should be considered to have a +0 lock count.
  • Values you get handed back from invoking framework or invoking user functions will have a +1 lock count.
  • Values you get handed back from REALGetPropValue will have a +1 lock count.
  • Values you return from your function should have a +1 lock count – unless you raise an exception.
  • Calling unlock decrements the lock count by 1, potentially deallocating the object.
  • There is no garbage collector, so avoid reference cycles.

So, let’s put a bunch of these rules into practice in a bit of a contrived example:

// Takes a FolderItem and returns its path with a trailing slash if it's a directory.
static REALstring PathWithTrailingSlash(REALobject instance)
{
  Boolean isDirectory;
  assert( REALGetPropValue( instance, "Directory", &isDirectory ));

  REALstring nativePath;
  assert( REALGetPropValue( instance, "NativePath", &nativePath ) );
  
  if (!isDirectory) {
  	return nativePath;
  }

  REALstring result = NULL;
  REALstringData pathData;
  if (REALGetStringData( nativePath, kREALTextEncodingUTF8, &pathData )) {
  	if (pathData.length) {
  		const char *utf8Data = static_cast<const char *>(pathData.data);
  		if (utf8Data[pathData.length - 1] != '/') {
  			REALstring temp = REALBuildString("/", 1, kREALTextEncodingUTF8);
  			result = REALAddStrings( nativePath, temp );
  			REALUnlockString( temp );
  		} else {
  			// We're unlocking nativePath before we return, so we need to bump
  			// the lock count on this here.
  			result = nativePath;
  			REALLockString( result );
  		}
  	}

  	REALDisposeStringData( &pathData );
  }

  // If anything failed and result is still NULL when we return, that's okay.
  // NULL is the canonical way of representing an empty string.
  REALUnlockString( nativePath );
  return result;
}

Note that this elides some error checking for simplicity. For example, you should check ‘instance’ for NULL and raise a NilObjectException.