Tech Note 05d: Enhancements to Shared LibrariesNovember 25, 2003© NSB Corporation. All rights reserved. |
Examples:
Err Multiply(UInt16 refNum, double src1, double src2, double *ret) { *ret = src1 + src2; return 0; }This is suitable for use within NSBasic as a function. (With the new shared library mechanism, it can also be defined as a subroutine.)
Err ChangeStatus(UInt16 refNum, Int32 in) { if (in) TurnOn(); else TurnOff(); return 0; }This is suitable for use as a subroutine. It is an error to use a subroutine in place of a function or a function in place of a subroutine.
Accompanying this is a .inf file, which specifies how the library is to be used within NSBasic. A typical .inf file might look like this:
[General] ExtensionName=Test-library PrcName=TestLib.prc Version=2.0 Beta Manufacturer=NSBasic.com [GlobalMethods] Multiply=1,func,2,"" ChangeStatus=2,proc,1, ""The last two lines are the important ones. They say that Multiply is the first subroutine or function, it's a function, and takes 2 arguments. Also ChangeStatus is the second subroutine or function, is a subroutine, and takes one argument.
Under the old mechanism, NSBasic had to guess the types of the arguments based on the types of arguments supplied to the expression in NSBasic. This made many shared libraries unusable, prevented returning numeric values other than by the return function, required excessive care when choosing how to call functions, and resulted in some rather nasty bugs in cases where it is difficult to tell the type from within an NSBasic use.
[General] ExtensionName=Test-library PrcName=TestLib.prc Version=2.0 Beta Manufacturer=NSBasic.com InfVers=2.0 [GlobalMethods] Multiply=1,func,2,"Multiply(in a as double, in b as double) as double" ChangeStatus=2,proc,1, "ChangeStatus(in a as integer)"The only difference is that the material within the double quotes, which used to be blank or unimportant, is now used to describe the function, including its types. Everything following this description is ignored, so it is possible to use this string as documentation.
The function or subroutine description looks much like a declaration in NSBasic, with a few twists to give the compiler important information. Consider the ChangeStatus subroutine:
ChangeStatus(in a as integer)This specifies a subroutine that takes one argument. "In a as integer" means that there is an argument, a, which is an integer. It also specifies that this is an in parameter. There are three kinds of arguments: in, out, and inout (one word). In parameters are used when a value should just go in the shared library function. In the case of in arguments, the C code of the shared library expects a simple type such as double or Int16. Out and inout parameters are used when a value must come out of or go in and come out of a shared library function. In the case of out and inout arguments, the C code expects a pointer to a type, such as double* or Int16.
Types are not case sensitive.
To make .inf files accessible both to people who prefer C and to people who prefer NSBasic, some types can be specified in many ways:
NSBasic Type C Type .inf types ------------ ------ ---------- Integer long, Int32 integer, int32, int4, long Short short short, int16, int2, int Float double double, flt8, flt64 Single float float, single, flt4, flt32 String char * string, char Variant ? * variantNote that there is possible confusion over "float," as a Float in NSBasic is a 64-bit floating point number, while float in C is a 32-bit floating point number.
A function is specified just as a subroutine, except that it has a type specifier at the end:
Multiply(in a as double, in b as double) as doubleWhether an argument is in, out, or inout, no matter what type of argument or return value, types are converted between C and NSBasic types automatically.
Note that inout parameters open up more functionality than before. Consider a shared library function that looks like this in C:
Err DoIt(UInt16 refNum, double src1, double *ret, double src2)Assuming that ret is supposed to be a return argument, there was previously no way to retrieve this value. Using inout parameters, it's simple:
DoIt(in src1 as double, inout ret as double, in src2 as double)Calling this in NSBasic as
TestLib.DoIt(3, x, 4)will result in x holding the value that DoIt stuffed into ret, suitably type-converted to whatever numeric type x has as a variable.
This applies to both new- and old-style shared libraries.
/** Yuk generator after Doug Lee Returns a sentence of n Yuks Yuk(3) => "Yuk, yuk, yuk." Yuk(1) => "Yuk." */ Err Yuk(UInt16 refNum, short in, char *ret) { char *s; int k; if (in <= 0) return 0; s = ret; for (k = 0; k < in; ++k) { if (k) { *s++ = ','; *s++ = ' '; } *s++ = k ? 'y' : 'Y'; *s++ = 'u'; *s++ = 'k'; } *s++ = '.'; *s = '\0'; return 0; // No error }Depending on the .inf file, this could be a function of one short argument that returns a string or a subroutine of two arguments, one short, and one string, which modifies the second argument. For the purposes of this discussion, it does not matter which way it is defined in the .inf file.
The problem with this is that it stores the resuting string starting at *ret, and there is no way to know how long the memory is. The original shared library code ensured that there were at least 300 bytes or the size of the previous returned string, whichever was longer. However, this is not enough for some applications.
Way 1 is most useful when the string is to be made on the fly based on the input data.
Consider Yuk rewritten this way:
/** Yuk generator after Doug Lee Returns a sentence of in Yuks Yuk(3) => "Yuk, yuk, yuk." Yuk(1) => "Yuk." */ Err Yuk(UInt16 refNum, short in, Char *ret) { char *s; int k; if (in <= 0) return 0; /* NEW CODE */ { MemHandle m = MemPtrRecoverHandle(ret - sizeof(Char *)); MemHandleUnlock(m); if (0 != MemHandleResize(m, in * 5 * sizeof(Char) + sizeof(Char *))) { ErrFatalDisplay("Resize failed in Yuk."); } s = MemHandleLock(m); *((Char **) s) = s + sizeof(Char *); s += sizeof(Char *); } /* END NEW CODE */ for (k = 0; k < in; ++k) { if (k) { *s++ = ','; *s++ = ' '; } *s++ = k ? 'y' : 'Y'; *s++ = 'u'; *s++ = 'k'; } *s++ = '.'; *s = '\0'; return 0; // No error }For clarity, the new code is in a separate block. It replaces "s = ret;" in the previous version. Here it is, line by line:
MemHandle m = MemPtrRecoverHandle(ret - sizeof(Char *));Subtract sizeof(Char *) from ret to make it point to the beginning of this pointer and recover the handle associated with it.
MemHandleUnlock(m);The handle will be locked upon entry into the subroutine. Unlock it. Note that from now on the value given by ret is unreliable and should not be used.
if (0 != MemHandleResize(m, in * 5 * sizeof(Char) + sizeof(Char *))) { ErrFatalDisplay("Resize failed in Yuk."); }The structure of the Yuk means that each Yuk uses five characters, except for the last, which uses only four. With one extra character needed for the end of string, in * 5 is the right number of bytes for the string part. It's multiplied by sizeof(Char), which should always be 1 if Char is the same size as char for purely paranoid reasons. The addition of sizeof(Char *) is for the pointer that needs to be at the beginning of the block of memory.
This is only an example; production code should probably have more graceful error recovery than just a fatal display.
s = MemHandleLock(m);Relock the handle, putting the pointer into s. From now on, s will be used exclusively, and ret will not be used.
*((Char **) s) = s + sizeof(Char *);At the first part of *s is a pointer to the rest of s, put it there. If you don't do this, NSBasic will be unable to find the string.
s += sizeof(Char *);Advance s beyond this pointer, because for the rest of the function we are going to use it as a pointer to the string.
Way 2 is useful when the return string is fixed in memory and will not change at least until the next shared library call. Perhaps the string is in a database. Perhaps it is some string that is managed by the shared library in some sort of "global" string.
Here is Yuk rewritten this way:
** Yuk generator after Doug Lee Returns a sentence of in Yuks Yuk(3) => "Yuk, yuk, yuk." Yuk(1) => "Yuk." */ Err Yuk(UInt16 refNum, short in, Char *ret) { char *s; int k; if (in <= 0) return 0; ret -= sizeof(Char *); * ((Char **) ret) = HaveSomeYuks(in); return 0; // No error }HaveSomeYuks is deliberately undefined, but it should be a function that returns a pointer to some static string.
As part of the return process, this string will be copied into an NSBasic string. However, for long strings, at least this way of writing the function does not use up extra temporary memory beyond this.