Name Association {R}←{X}⎕NA Y
⎕NA
provides access from APL to compiled functions within a library. A library is implemented according to the Operating System as follows:
- a Dynamic Link Library(DLL) under Windows
- a Shared Library (.so or .dylib) under Linux or macOS
- a static library (.a) under AIX
A DLL1 is a collection of functions typically written in C (or C++) each of which may take arguments and return a result.
Instructional examples using ⎕NA
can be found in the supplied workspace quadna
.
The DLL may be part of the standard operating system software, a library purchased from a third party supplier, or one that you have written yourself.
The right argument Y
is a character vector that identifies the name and syntax of the function to be associated. The left argument X
is a character vector that contains the name to be associated with the external function. If the ⎕NA
is successful, a function (name class 3) is established in the active workspace with name X
. If X
is omitted, the name of the external function itself is used for the association.
The shy result R
is a character vector containing the name of the external function that was fixed.
For example, math.dll
might be a library of mathematical functions containing a function divide
.
In a compiled language such as C, the types of arguments and results of functions must be declared explicitly. Typically, these types will be published with the documentation that accompanies the DLL. For example, function divide
might be declared:
double divide(int32_t, int32_t);
which means that it expects two long (4-byte) integer arguments and returns a double (8-byte) floating point result. Notice the correspondence between the C declaration and the right argument of ⎕NA
:
C: double divide (int32_t, int32_t);
APL: 'div' ⎕NA 'F8 math|divide I4 I4 '
So to associate the APL name div
with this external function:
'div' ⎕NA 'F8 math|divide I4 I4'
where F8
and I4
, specify the types of the result and arguments expected by divide
. The association has the effect of establishing a new function: div
in the workspace, which when called, passes its arguments to divide
and returns the result.
)fns
div
div 10 4
2.5
It is imperative that care be taken when coding type declarations. A DLL cannot check types of data passed from APL. A wrong type declaration will lead to erroneous results or may even cause the workspace to become corrupted and crash. During development, you may wish to prevent this happening. See: ErrorOnExternalException.
The full syntax for the right argument of ⎕NA
is:
[result] library|function [arg1] [arg2] ...
Note that functions associated with DLLs are never dyadic. All arguments are passed as items of a (possibly nested) vector on the right of the function.
Locating the DLL
The DLL may be specified using a full pathname, file extension, and function type.
Be aware
A 32-bit interpreter can only load 32-bit DLLs/shared libraries; a 64-bit interpreter can only load 64-bit DLLs/shared libraries.
If a DLL/shared library has a missing dependency, the error generated by the operating system, and therefore reported by Dyalog will suggest that the DLL/shared library that was explicitly called in the ⎕NA
call is missing.
Pathname
APL uses the LoadLibrary()
system function under Windows or dlopen()
under UNIX, Linux and macOS to load the DLL. If a full or relative pathname is omitted, these functions search a list of directories determined by the operating system. This list always includes the directory which contains the Dyalog program, and on all non-Windows platforms, $DYALOG/lib. For further details, see the operating system documentation about these functions.
Alternatively, a full or relative pathname may be supplied in the usual way:
⎕NA'... c:\mydir\mydll|foo ...'
Errors
If the specified DLL (or a dependent DLL) fails to load it will generate:
FILE ERROR 2 No such file or directory
It is frequently the case that this error is a result of a missing dependency; operating systems do not return error codes which allow the interpreter to generate a more specific error.
If the DLL loads successfully, but the specified library function is not accessible, it will generate:
VALUE ERROR
File Extension
Under Windows, if the file extension is omitted, .dll is assumed. Note that some DLLs are in fact .exe files, and in this case the extension must be specified explicitly:
⎕NA'... mydll.exe|foo ...'
libc.a on Non-Windows Platforms
On non-Windows platforms many of the most useful system library functions appear in libc.a
. The quadna
workspace includes the function NonWindows.Setup
which has code which will locate libc.a
on each platform.
Data Type Coding Scheme
The type coding scheme introduced above is of the form:
[direction] [special] type [width] [array][[count]]
The options are summarised in the following table and their functions detailed below.
Description | Symbol | Meaning |
---|---|---|
Direction | < |
Pointer to array input to DLL function. |
> |
Pointer to array output from DLL function | |
= |
Pointer to input/output array. | |
Special | 0 |
Null-terminated string. |
# |
Byte-counted string | |
Type | I |
int |
U |
unsigned int | |
C |
char | |
T |
char 1 | |
F |
float | |
D |
decimal | |
J |
complex | |
P |
uintptr-t 2 | |
A |
APL array | |
Z |
APL array with header (as passed to a TCP/IP socket) | |
Width | 1 |
1-byte |
2 |
2-byte | |
4 |
4-byte | |
8 |
8-byte | |
16 |
16-byte (128-bit) | |
Array | [n] |
Array of length n elements |
[] |
Array, length determined at call-time | |
Structure | {...} |
Structure. |
Count | [int] |
Rather than explicitly declaring multiple adjacent occurrences of the same the count option may be used |
In the Classic Edition, C
specifies untranslated character, whereas T
specifies that the character data will be translated to/from ⎕AV
.
In the Unicode Edition, C and T are identical (no translation of character data is performed) except that for C the default width is 1 and for T the default width is "wide" (2 bytes under Windows, 4 bytes under UNIX, Linux or macOS).
The use of T with default width is recommended to ensure portability between Editions.
Direction
C functions accept data arguments either by value or by address. This distinction is indicated by the presence of a '*
' or '[]
' in the argument declaration:
int num1; // value of num1 passed.
int *num2; // Address of num2 passed.
int num3[]; // Address of num3 passed.
An argument (or result) of an external function of type pointer, must be matched in the ⎕NA
call by a declaration starting with one of the characters: <
, >
, or =
.
In C, when an address is passed, the corresponding value can be used as either an input or an output variable. An output variable means that the C function overwrites values at the supplied address. Because APL is a call-by-value language, and doesn't have pointer types, we accommodate this mechanism by distinguishing output variables, and having them returned explicitly as part of the result of the call.
This means that where the C function indicates a pointer type, we must code this as starting with one of the characters: <
, >
or =
.
< |
indicates that the address of the argument will be used by C as an input variable and values at the address will not be over-written. |
> |
indicates that C will use the address as an output variable. In this case, APL must allocate an output array over which C can write values. After the call, this array will be included in the nested result of the call to the external function. |
= |
indicates that C will use the address for both input and output. In this case, APL duplicates the argument array into an output buffer whose address is passed to the external function. As in the case of an output only array, the newly modified copy will be included in the nested result of the call to the external function. |
Examples
<I2 |
Pointer to 2-byte integer - input to external function |
>C |
Pointer to character output from external function. |
=T |
Pointer to character input to and output from function. |
=A |
Pointer to APL array modified by function. |
Special
In C it is common to represent character strings as null-terminated or byte counted arrays. These special data types are indicated by inserting the symbol 0
(null-terminated) or #
(byte counted) between the direction indicator (<
, >
, =
) and the type (T
or C)
specification. For example, a pointer to a null-terminated input character string is coded as <0T[]
, and an output one coded as >0T[]
.
Note that while appending the array specifier '[]
' is formally correct, because the presence of the special qualifier (0
or #
) implies an array, the '[]
' may be omitted: <0T
, >0T
, =#C
, etc.
Note also that the 0 and # specifiers may be used with data of all types (excluding A
and Z
) and widths. For example, in the Classic Edition, <0U2
may be useful for dealing with Unicode.
Type
The data type of the argument may be one of the following characters and may be specified in lower or upper case:
Code | Type | Description |
---|---|---|
I |
Integer | The value is interpreted as a 2s complement signed integer |
U |
Unsigned integer | The value is interpreted as an unsigned integer |
C |
Character | The value is interpreted as a character. In the Unicode Edition, the value maps directly onto a Unicode code point. In the Classic Edition, the value is interpreted as an index into ⎕AV . This means that ⎕AV positions map onto corresponding ANSI positions . For example, with ⎕IO=0 : ⎕AV[35] = 's' , maps to ANSI[35] = ' |
T |
Translated character | The value is interpreted as a character. In the Unicode Edition, the value maps directly onto a Unicode code point. In the Classic Edition, the value is translated using standard Dyalog ⎕AV to ANSI translation. This means that ⎕AV characters map onto corresponding ANSI characters . For example, with ⎕IO=0 : ⎕AV[35] = 's' maps to ANSI[115] = 's' |
UTF |
Unicode encoded | >0UTF8[] will translate to a UTF-8 encoded string <0UTF16[] will translate from a UTF-16LE encoded string |
F |
Float | The value is interpreted as an IEEE 754-2008 binary64 floating point number |
D |
Decimal | The value is interpreted as an IEEE 754-2008 decimal128 floating point number (DPD format on AIX, BID format on other platforms) |
J |
Complex | |
P |
uintptr-t | This is equivalent to U4 on 32-bit versions and U8 on 64-bit versions |
∇ |
Function pointer | This allows the passing of an APL function for the function to call |
A |
APL array | This is the same format as is used to transmit APL arrays to an Auxiliary Processor (AP) |
Z |
APL array with header | This is the same format as is used to transmit APL arrays over TCP/IP Sockets |
Width
The type specifier may be followed by the width of the value in bytes. For example:
I4 |
4-byte signed integer. |
U2 |
2-byte unsigned integer. |
F8 |
8-byte floating point number. |
F4 |
4-byte floating point number. |
D16 |
16-byte decimal floating-point number |
Type | Possible values for Width | Default value for Width |
---|---|---|
I |
1, 2, 4, 8 | 4 |
U |
1, 2, 4, 8 | 4 |
C |
1,2,4 | 1 |
T |
1,2,4 | wide character(see below) |
UTF |
8,16 | none |
F |
4, 8 | 8 |
D |
16 | 16 |
J |
16 | 16 |
P |
Not applicable | |
∇ |
Not applicable | |
A |
Not applicable | |
Z |
Not applicable |
In the Unicode Edition, the default width is the width of a wide character according to the convention of the host operating system. This translates to T2 under Windows and T4 under UNIX, Linux or macOS.
Note that 32-bit versions can support 64-bit integer arguments, but not 64-bit integer results.
Examples
I2 |
16-bit integer |
<I4 |
Pointer to input 4-byte integer |
U |
Default width unsigned integer |
=F4 |
Pointer to input/output 4-byte floating point number. |
Arrays
Arrays are specified by following the basic data type with [n]
or []
, where n
indicates the number of elements in the array. In the C declaration, the number of elements in an array may be specified explicitly at compile time, or determined dynamically at runtime. In the latter case, the size of the array is often passed along with the array, in a separate argument. In this case, n
, the number of elements is omitted from the specification. Note that C deals only in scalars and rank 1 (vector) arrays.
int vec[10]; // explicit vector length.
unsigned size, list[]; // undetermined length.
could be coded as:
I[10] |
vector of 10 ints. |
U U[] |
unsigned integer followed by an array of unsigned integers. |
Confusion sometimes arises over a difference in the declaration syntax between C and ⎕NA
. In C, an argument declaration may be given to receive a pointer to either a single scalar item, or to the first element of an array. This is because in C, the address of an array is deemed to be the address of its first element.
void foo (char *string);
char ch = 'a', ptr = "abc";
foo(&ch);// call with address of scalar.
foo(ptr);// call with address of array.
However, from APL's point of view, these two cases are distinct and if the function is to be called with the address of (pointer to) a scalar, it must be declared: '<T'
. Otherwise, to be called with the address of an array, it must be declared: '<T[]'
. Note that it is perfectly acceptable in such circumstances to define more than one name association to the same DLL function specifying different argument types:
'FooScalar'⎕NA'mydll|foo <T' ⋄ FooScalar'a'
'FooVector'⎕NA'mydll|foo <T[]' ⋄ FooVector'abc'
Structures
Arbitrary data structures, which are akin to nested arrays, are specified using the symbols {}
. For example, the code {F8 I2}
indicates a structure comprised of an 8-byte float followed by a 2-byte int. Furthermore, the code <{F8 I2}[3]
means an input pointer to an array of 3 such structures.
For example, this structure might be defined in C thus:
typedef struct
{
double f;
short i;
} mystruct;
A function defined to receive a count followed by an input pointer to an array of such structures:
void foo(unsigned count, mystruct *str);
An appropriate ⎕NA
declaration would be:
⎕NA'mydll.foo U <{F8 I2}[]'
A call on the function with two arguments - a count followed by a vector of structures:
foo 4,⊂(1.4 3)(5.9 1)(6.5 2)(0 0)
Notice that for the above call, APL converts the two Boolean (0 0)
elements to an 8-byte float and a 2-byte int, respectively.
Note that if the C compiler would add extra space for alignment within a structure the ⎕NA
syntax will need to code that explicitly. For example:
typedef struct
{
short i;
/* most C compilers would add 6 bytes of alignment here */
double d;
} mystruct;
An appropriate ⎕NA
declaration would be:
⎕NA'mydll.foo U <{I2 {I1[6]} F8}[]'
A call on the function with two arguments - a count followed by a vector of structures:
pad←⊂6⍴0
foo 4,⊂(3 pad 1.4)(1 pad 5.9 )(2 pad 6.5 )(0 pad 0)
A library designer tries to avoid defining structures that induce padding.
Count
If a definition includes multiple adjacent occurrences of the same item, the count syntax may be used rather than explicitly repeating the same definition.
For example:
>I8[3]
rather than >I8 >I8 >I8
{I8 U8 I8 P}[2]
rather than {I8 U8 I8 P} {I8 U8 I8 P}
Using a Function
A DLL function may or may not return a result, and may take zero or more arguments. This syntax is reflected in the coding of the right argument of ⎕NA
. However, notice that the corresponding associated APL function is a result-returning niladic (if it takes no arguments) or monadic function. It cannot be dyadic and it must always return a vector result - a null one if there is no output from the DLL function. See Result Vector section below. Examples of the various combinations are:
DLL function Non-result-returning
⎕NA 'mydll|fn1' ⍝ Niladic
⎕NA 'mydll|fn2 <0T' ⍝ Monadic - 1-element arg
⎕NA 'mydll|fn3 =0T <0T' ⍝ Monadic - 2-element arg
DLL function Result-returning
⎕NA 'I4 mydll|fn4' ⍝ Niladic
⎕NA 'I4 mydll|fn5 F8' ⍝ Monadic - 1-element arg
⎕NA 'I4 mydll|fn6 >I4[] <0T'⍝ Monadic - 2-element arg
When the external function is called, the number of elements in the argument must match the number defined in the ⎕NA
definition. Using the examples above:
fn1 ⍝ Niladic Function.
fn2, ⊂'Single String' ⍝ 1-element arg
fn3 'This' 'That' ⍝ 2-element arg
Note in the second example, that you must enclose the argument string to produce a single item (nested) array in order to match the declaration. Dyalog converts the type of a numeric argument if necessary, so for example in fn5
defined above, a Boolean value would be converted to double floating point (F8) prior to being passed to the DLL function.
Multi-Threading
Appending the '&
' character to the function name causes the external function to be run in its own system thread. For example:
⎕NA'... mydll|foo& ...'
This means that other APL threads can run concurrently with the one that is calling the ⎕NA
function.
Name Mangling
C++ and some other languages will by default mangle (or decorate) function names which are exported from a DLL file. The given external function name must exactly match the exported name, either by matching the name mangling or by ensuring the names exported from the library are not mangled.
Call by Ordinal Number
Under Windows, a DLL may associate an ordinal number with any of its functions. This number may then be used to call the function as an alternative to calling it by name. Using ⎕NA
to call by ordinal number uses the same syntax but with the function name replaced with its ordinal number. For example:
⎕NA'... mydll|57 ...'
Pointer Arguments
When passing pointer arguments there are three cases to consider.
<
Input pointer
In this case you must supply the data array itself as argument to the function. A pointer to its first element is then passed to the DLL function.
fn2 ⊂'hello'
>
Output pointer
Here, you must supply the number of elements that the output will need in order for APL to allocate memory to accommodate the resulting array.
fn6 10 'world' ⍝ 1st arg needs space for 10 ints.
Note that if you were to reserve fewer elements than the DLL function actually used, the DLL function would write beyond the end of the reserved array and may cause the interpreter to crash with a System Error (syserror 999 on Windows or SIGSEGV on UNIX, Linux or macOS).
=
Input/Output
As with the input-only case, a pointer to the first element of the argument is passed to the DLL function. The DLL function then overwrites some or all of the elements of the array, and the new value is passed back as part of the result of the call. As with the output pointer case, if the input array were too short, so that the DLL wrote beyond the end of the array, the interpreter would almost certainly crash.
fn3 '.....' 'hello'
Specifying Pointers Explicitly
⎕NA
syntax enables APL to pass arguments to DLL functions by value or address as appropriate. For example if a function requires an integer followed by a pointer to an integer:
void fun(int value, int *addr);
You might declare and call it:
⎕NA'mydll|fun I <I' ⋄ fun 42 42
The interpreter passes the value of the first argument and the address of the second one.
Two common cases occur where it is necessary to pass a pointer explicitly. The first is if the DLL function requires a null pointer, and the second is where you want to pass on a pointer which itself is a result from a DLL function.
In both cases, the pointer argument should be coded as P
. This causes APL to pass the pointer unchanged, by value, to the DLL function.
In the previous example, to pass a null pointer, (or one returned from another DLL function), you must code a separate ⎕NA
definition.
'fun_null'⎕NA'mydll|fun I P' ⋄ fun_null 42 0
Now APL passes the value of the second argument (in this case 0 - the null pointer), rather than its address.
Note that by using P, which is 4-byte for 32-bit processes and 8-byte for 64-bit processes, you will ensure that the code will run unchanged under both 32-bit and 64-bit versions of Dyalog APL.
Result Vector
In APL, a function cannot overwrite its arguments. This means that any output from a DLL function must be returned as part of the explicit result, and this includes output via 'output' or 'input/output' pointer arguments.
The general form of the result from calling a DLL function is a nested vector. The first item of the result is the defined explicit result of the external function, and subsequent items are implicit results from output, or input/output pointer arguments.
The length of the result vector is therefore: 1 (if the function was declared to return an explicit result) + the number of output or input/output arguments.
⎕NA Declaration |
Result | Output Arguments | Result Length |
---|---|---|---|
mydll|fn1 |
0 |
0 |
|
mydll|fn2 <0T |
0 |
0 |
0 |
mydll|fn3 =0T <0T |
0 |
1 0 |
1 |
I4 mydll|fn4 |
1 |
1 |
|
I4 mydll|fn5 F8 |
1 |
0 |
1 |
I4 mydll|fn6 >I4[] <0T |
1 |
1 0 |
2 |
Note that the result vector from a function that is declared void()
and has no output parameters is ⍬
(zilde).
As a convenience, if the result would otherwise be a 1-item vector, it is disclosed. Using the third example above:
⍴fn3 '.....' 'abc'
5
fn3
has no explicit result; its first argument is input/output pointer; and its second argument is input pointer. Therefore as the length of the result would be 1, it has been disclosed.
64 bit integer results
When a 64 bit integer result is returned it is converted into 128 bit decimal floating point, because this is the only APL data type that can fully preserve all 64 bits of the result. If you wish to perform arithmetic with this value, you must set ⎕FR
to 1287 in order to preserve the same precision. If this is not done then the precision will be 53 bits which might not be enough.
Callbacks (∇
)
Currently, support for a ⎕NA
function to call an APL function is limited to the use of the NAG (National Algorithms Group) library of functions. This library is a FORTRAN library and FORTRAN passes arguments by reference (address) rather than by value. The expression:
∇f8←(P P P P)
declares a callback function that returns a double and takes 4 pointer arguments. The result can be any of the normal results. It is not possible to return a pointer. The arguments can be from 0 to 16 P values.
The argument when passed can be the name of an APL function or the ⎕OR
of a function.
The function when called can then decode the pointer arguments appropriately using a ⎕NA
of MEMCPY()
.
ANSI /Unicode Versions of Library Calls
Under Windows, most library functions that take character arguments, or return character results have two forms: one Unicode (Wide) and one ANSI. For example, a function such as MessageBox()
, has two forms MessageBoxA()
and MessageBoxW()
. The A
stands for ANSI (1-byte) characters, and the W
for wide (2-byte Unicode) characters.
It is essential that you associate the form of the library function that is appropriate for the Dyalog Edition you are using, that is, MessageBoxA()
for the Classic Edition, but MessageBoxW()
for the Unicode Edition.
Whilst this is convenient it is not complete. It is adequate for character arrays that consist of characters from UCS-2 (that is, those that will fit in an array with a ⎕DR
of 80 or 160). If a more complete support is required then the W form of the function would be required and explicit use of UTF16 specified.
To simplify writing portable code for both Editions, you may specify the character *
instead of A
or W
at the end of a function name. This will be replaced by A
in the Classic Edition and W
in the Unicode Edition.
The default name of the associated function (if no left argument is given to ⎕NA
), will be without the trailing letter (MessageBox
).
Type Definitions
The C language encourages the assignment of defined names to primitive and complex data types using its #define
and typedef
mechanisms. Using such abstractions enables the C programmer to write code that will be portable across many operating systems and hardware platforms.
Windows software uses many such names and Microsoft documentation will normally refer to the type of function arguments using defined names such as HANDLE
or LPSTR
rather than their equivalent C primitive types: int
or char*
.
It is beyond the scope of this manual to list all the Microsoft definitions and their C primitive equivalents, and indeed, DLLs from sources other than Microsoft may well employ their own distinct naming conventions.
In general, you should consult the documentation that accompanies the DLL in order to convert typedefs to primitive C types and thence to ⎕NA
declarations. The documentation may well refer you to the 'include' files which are part of the Software Development Kit, and in which the types are defined.
The following table of some commonly encountered Windows typedefs and their ⎕NA
equivalents might prove useful.
Windows typedef | ⎕NA equivalent |
---|---|
HWND | P |
HANDLE | P |
GLOBALHANDLE | P |
LOCALHANDLE | P |
DWORD | U4 |
WORD | U2 |
BYTE | U1 |
LPSTR | =0T[] (note 1) |
LPCSTR | <0T[] (note 2) |
WPARAM | U (note 3) |
LPARAM | U4 (note 3) |
LRESULT | I4 |
BOOL | I |
UINT | U |
ULONG | U4 |
ATOM | U2 |
HDC | P |
HBITMAP | P |
HBRUSH | P |
HFONT | P |
HICON | P |
HMENU | P |
HPALETTE | P |
HMETAFILE | P |
HMODULE | P |
HINSTANCE | P |
COLORREF | {U1[4]} |
POINT | {I I} |
POINTS | {I2 I2} |
RECT | {I I I I} |
CHAR | T or C |
Notes
LPSTR
is a pointer to a null-terminated string. The definition does not indicate whether this is input or output, so the safest coding would be=0T[]
(providing the vector you supply for input is long enough to accommodate the result). You may be able to improve simplicity or performance if the documentation indicates that the pointer is 'input only' (<0T[]
) or 'output only' (>0T[]
). See Direction above.LPCSTR
is a pointer to a constant null-terminated string and therefore coding<0T[]
is safe.-
WPARAM
is an unsigned value,LPARAM
is signed. They are 32 bit values in a 32-bit APL, and 64-bit in a 64 bit APL. You should consult the documentation for the specific function that you intend to call to determine what type they represent -
The use of type T with default width ensures portability of code between Classic and Unicode Editions. In the Classic Edition, T (with no width specifier) implies 1-byte characters which are translated between
⎕AV
and ASCII, while in the Unicode Edition, T (with no width specifier) implies 2-byte (Unicode) characters.
The Dyalog DLL
The Dyalog DLL (see Run-Time Applications and Components) contains three functions: MEMCPY, STRNCPY and STRLEN.
MEMCPY
MEMCPY
is an extremely versatile function used for moving arbitrary data between memory buffers.
Its C definition is:
void *MEMCPY( // copy memory
void *to, // target address
void *fm, // source address
size_t size // number of bytes to copy
);
MEMCPY
copies size
bytes starting from source address fm
, to destination address to
. The source and destination areas should not overlap; if they do the behaviour is undefined and the result is the first argument.
MEMCPY
's versatility stems from being able to associate to it using many different type declarations.
Example
Suppose a global buffer (at address: addr
) contains (numb
) double floating point numbers. To copy these to an APL array, we could define the association:
'doubles' ⎕NA 'dyalog32|MEMCPY >F8[] I4 U4'
doubles numb addr (numb×8)
Notice that:
- As the first argument to
doubles
is an output argument, we must supply the number of elements to reserve for the output data. MEMCPY
is defined to take the number of bytes to copy, so we must multiply the number of elements by the element size in bytes.
Example
Suppose that a database application requires that we construct a record in global memory prior to writing it to file. The record structure might look like this:
typedef struct {
int empno; // employee number.
float salary; // salary.
char name[20]; // name.
} person;
Then, having previously allocated memory (addr
) to receive the record, we can define:
'prec' ⎕NA 'dyalog64|MEMCPY P <{P F4 T[20]} P'
prec addr(99 12345.60 'Charlie Brown')(4+4+20)
STRNCPY
STRNCPY
is used to copy null-terminated strings between memory buffers.
Its C definition is:
void *STRNCPY( // copy null-terminated string
char *to, // target address
char *fm, // source address
size_t size // MAX number of chars to copy
);
STRNCPY
copies a maximum of size
characters from the null-terminated source string at address fm
, to the destination address to
. If the source and destination strings overlap, the result is the first argument.
If the source string is shorter than size
, a null character is appended to the destination string.
If the source string (including its terminating null) is longer than size
, only size
characters are copied and the resulting destination string is not null-terminated
Example
Suppose that a database application returns a pointer (addr
) to a structure that contains two (max 20-char) null-terminated strings.
typedef struct { // null-terminated strings:
char first[20]; // first name (max 19 chars + 1 null).
char last[20]; // last name. (max 19 chars + 1 null).
} name;
To copy the names from the structure:
'get'⎕NA'dyalog64|STRNCPY >0T1[] P U4'
get 20 addr 20
Charlie
get 20 (addr+20) 20
Brown
Note that (as this is a 64-bit example), ⎕FR
must be 1287 for the addition to be reliable.
To copy data from the workspace into an already allocated (new
) structure:
'put'⎕NA'dyalog32|STRNCPY I4 <0T[] U4'
put new 'Bo' 20
put (new+4) 'Peep' 20
Notice in this example that you must ensure that names no longer than 19 characters are passed to put
. More than 19 characters would not leave STRNCPY enough space to include the trailing null, which would probably cause the application to fail.
STRNCPYA
This is a synonym for STRNCPY. It is there so that STRNCPY* (on Windows) selects between STRNCPYA and STRNCPYW.
STRNCPYW
This is a cover for the C standard function wcsncpy()
. It is named this way so that (on Windows) STRNCPY* will behave helpfully.
STRLEN
STRLEN
calculates the length of a C string (a 0-terminated string of bytes in memory). Its C declaration is:
size_t STRLEN( // calculate length of string
const char *s // address of string
);
Example
Suppose that a database application returns a pointer (addr
) to a null-terminated string and you do not know the upper bound on the length of the string.
To copy the string into the workspace:
'len'⎕NA'P dyalog32|STRLEN P'
'cpy'⎕NA'dyalog32|MEMCPY >T[] P P'
cpy l addr (l←len addr)
Bartholemew
Examples
The following examples all use functions from the Microsoft Windows user32.dll
.
This DLL should be located in a standard Windows directory, so you should not normally need to give the full path name of the library. However if trying these examples results in the error message FILE ERROR 1 No such file or directory
, you must locate the DLL and supply the full path name (and possibly extension).
GetCaretBlinkTime()
The Windows function GetCaretBlinkTime
retrieves the caret blink rate. It takes no arguments and returns an unsigned int and is declared as follows:
UINT GetCaretBlinkTime(void);
The following statements would provide access to this routine through an APL function of the same name.
⎕NA 'U user32|GetCaretBlinkTime'
GetCaretBlinkTime
530
The following statement would achieve the same thing, but using an APL function called BLINK
.
'BLINK' ⎕NA 'U user32|GetCaretBlinkTime'
BLINK
530
SetCaretBlinkTime()
The Windows function SetCaretBlinkTime
sets the caret blink rate. It takes a single unsigned int
argument, does not return a result and is declared as follows:
void SetCaretBlinkTime(UINT);
The following statements would provide access to this routine through an APL function of the same name:
⎕NA 'user32|SetCaretBlinkTime U'
SetCaretBlinkTime 1000
MessageBox()
The Windows function MessageBox
displays a standard dialog box on the screen and awaits a response from the user. It takes 4 arguments. The first is the window handle for the window that owns the message box. This is declared as an unsigned int. The second and third arguments are both pointers to null-terminated strings containing the message to be displayed in the Message Box and the caption to be used in the window title bar. The 4th argument is an unsigned int that specifies the Message Box type. The result is an int which indicates which of the buttons in the message box the user has pressed. The function is declared as follows:
int MessageBox(HWND, LPCSTR, LPCSTR, UINT);
The following statements provide access to this routine through an APL function of the same name. Note that the 2nd and 3rd arguments are both coded as input pointers to type T null-terminated character arrays which ensures portability between Editions.
⎕NA 'I user32|MessageBox* P <0T <0T U'
The following statement displays a Message Box with a stop sign icon together with 2 push buttons labelled OK and Cancel (this is specified by the value 19).
MessageBox 0 'Message' 'Title' 19
The function works equally well in the Unicode Edition because the <0T specification is portable.
MessageBox 0 'Το Μήνυμα' 'Ο Τίτλος' 19
Note that a simpler, portable (and safer) method for displaying a Message Box is to use Dyalog APL's primitive MsgBox
object.
FindWindow()
The Windows function FindWindow
obtains the window handle of a window which has a given character string in its title bar. The function takes two arguments. The first is a pointer to a null-terminated character string that specifies the window's class name. However, if you are not interested in the class name, this argument should be a NULL pointer. The second is a pointer to a character string that specifies the title that identifies the window in question. This is an example of a case described above where two instances of the function must be defined to cater for the two different types of argument. However, in practice this function is most often used without specifying the class name. The function is declared as follows:
HWND FindWindow(LPCSTR, LPCSTR);
The following statement associates the APL function FW
with the second variant of the FindWindow call, where the class name is specified as a NULL pointer. To indicate that APL is to pass the value of the NULL pointer, rather than its address, we need to code this argument as I4
.
'FW' ⎕NA 'P user32|FindWindow* P <0T'
To obtain the handle of the window entitled "CLEAR WS - Dyalog APL/W":
⎕←HNDL←FW 0 'CLEAR WS - Dyalog APL/W'
59245156
GetWindowText()
The Windows function GetWindowText
retrieves the caption displayed in a window's title bar. It takes 3 arguments. The first is an unsigned int containing the window handle. The second is a pointer to a buffer to receive the caption as a null-terminated character string. This is an example of an output array. The third argument is an int which specifies the maximum number of characters to be copied into the output buffer. The function returns an int containing the actual number of characters copied into the buffer and is declared as follows:
int GetWindowText(HWND, LPSTR, int);
The following associates the "GetWindowText
" DLL function with an APL function of the same name. Note that the second argument is coded as ">0T
" indicating that it is a pointer to a character output array.
⎕NA 'I user32|GetWindowText* P >0T I'
Now change the Session caption using )WSID
:
)WSID MYWS
was CLEAR WS
Then retrieve the new caption (max length 255) using window handle HNDL
from the previous example:
]display GetWindowText HNDL 255 255
.→-------------------------.
| .→------------------. |
| 19 |MYWS - Dyalog APL/W| |
| '-------------------' |
'∊-------------------------'
There are three points to note.
- Firstly, the number 255 is supplied as the second argument. This instructs APL to allocate a buffer large enough for a 255-element character vector into which the DLL routine will write.
- Secondly, the result of the APL function is a nested vector of 2 elements. The first element is the result of the DLL function. The second element is the output character array.
- Finally, notice that although we reserved space for 255 elements, the result reflects the length of the actual text (19).
An alternative way of coding and using this function is to treat the second argument as an input/output array.
For example:
⎕NA 'I User32|GetWindowText* P =0T I'
]display GetWindowText HNDL (255⍴' ') 255
.→-------------------------.
| .→------------------. |
| 19 |MYWS - Dyalog APL/W| |
| '-------------------' |
'∊-------------------------'
In this case, the second argument is coded as =0T
, so when the function is called an array of the appropriate size must be supplied. This method uses more space in the workspace, although for small arrays (as in this case) the real impact of doing so is negligible.
GetCharWidth()
The function GetCharWidth
returns the width of each character in a given range. Its first argument is a device context (handle). Its second and third arguments specify font positions (start and end). The third argument is the resulting integer vector that contains the character widths (this is an example of an output array). The function returns a Boolean value to indicate success or failure. The function is defined as follows. Note that this function is provided in the library: gdi32.dll
.
BOOL GetCharWidth(HDC, UINT, UINT, LPINT);
The following statements provide access to this routine through an APL function of the same name:
⎕NA 'U4 gdi32|GetCharWidth* P U U >I[]'
'Prin'⎕WC'Printer'
]display GetCharWidth ('Prin' ⎕WG 'Handle') 65 67 3
.→-------------.
| .→-------. |
| 1 |50 50 50| |
| '~-------' |
'∊-------------'
Note: 'Prin'⎕WG'Handle'
returns a handle which is represented as a number. The number will be in the range (0 - 232] on a 32-bit version and (0 - 264] on a 64-bit version. These can be passed to a P type parameter. Older versions used a 32-bit signed integer.
The quadna
workspace
The following example from the supplied workspace: quadna.dws. quadna
illustrates several techniques which are important in advanced ⎕NA
programming. Function DllVersion
returns the major and minor version number for a given DLL. Note that this example assumes that the computer is running the 64-bit version of Dyalog.
In advanced DLL programming, it is often necessary to administer memory outside APL's workspace. In general, the procedure for such use is:
- Allocate global memory.
- Lock the memory.
- Copy any DLL input information from workspace into memory.
- Call the DLL function.
- Copy any DLL output information from memory to workspace.
- Unlock the memory.
- Free the memory.
Notice that steps 1 and 7 and steps 2 and 6 complement each other. That is, if you allocate global system memory, you must free it after you have finished using it. If you continue to use global memory without freeing it, your system will gradually run out of resources. Similarly, if you lock memory (which you must do before using it), then you should unlock it before freeing it. Although on some versions of Windows, freeing the memory will include unlocking it, in the interests of good style, maintaining the symmetry is probably a good thing.
∇ version←DllVersion file;Alloc;Free;Lock;Unlock;Size
;Info;Value;Copy;size;hndl;addr;buff;ok
[1]
[2] 'Alloc'⎕NA'P kernel32|GlobalAlloc U4 P'
[3] 'Free'⎕NA'P kernel32|GlobalFree P'
[4] 'Lock'⎕NA'P kernel32|GlobalLock P'
[5] 'Unlock'⎕NA'U4 kernel32|GlobalUnlock P'
[6]
[7] 'Size'⎕NA'U4 version|GetFileVersionInfoSize* <0T >U4'
[8] 'Info'⎕NA'U4 version|GetFileVersionInfo*<0T U4 U4 P'
[9] 'Value'⎕NA'U4 version|VerQueryValue* P <0T >P >U4'
[10]
[11] 'Copy'⎕NA'dyalog64|MEMCPY >U4[] P P'
[12]
[13] :If ×size←⊃Size file 0 ⍝ Size of info
[14] :AndIf ×hndl←Alloc 0 size ⍝ Alloc memory
[15] :If ×addr←Lock hndl ⍝ Lock memory
[16] :If ×Info file 0 size addr ⍝ Version info
[17] ok buff size←Value addr'\' 0 0 ⍝ Version value
[18] :If ok
[19] buff←Copy(size÷4)buff size ⍝ Copy info
[20] version←(2/2*16)⊤⊃2↓buff ⍝ Split version
[21] :EndIf
[22] :EndIf
[23] ok←Unlock hndl ⍝ Unlock memory
[24] :EndIf
[25] ok←Free hndl ⍝ Free memory
[26] :EndIf
∇
Lines [2-11] associate APL function names with the DLL functions that will be used.
Lines [2-5] associate functions to administer global memory.
Lines [7-9] associate functions to extract version information from a DLL.
Line[11] associates Copy
with MEMCPY
function from dyalog64.dll.
Lines [13-26] call the DLL functions.
Line [13] requests the size of buffer required to receive version information for the DLL. A size of 0 will be returned if the DLL does not contain version information.
Notice that care is taken to balance memory allocation and release:
On line [14], the :If clause is taken only if the global memory allocation is successful, in which case (and only then) a corresponding Free is called on line [25].
Unlock on line[23] is called if and only if the call to Lock on line [15] succeeds.
A result is returned from the function only if all the calls are successful Otherwise, the calling environment will sustain a VALUE ERROR
.
More Examples
⎕NA'I4 advapi32 |RegCloseKey P'
⎕NA'I4 advapi32 |RegCreateKeyEx* P <0T U4 <0T U4 U4 P >P >U4'
⎕NA'I4 advapi32 |RegEnumValue* P U4 >0T =U4 =U4 >U4 >0T =U4'
⎕NA'I4 advapi32 |RegOpenKey* P <0T >P'
⎕NA'I4 advapi32 |RegOpenKeyEx* P <0T U4 U4 >P'
⎕NA'I4 advapi32 |RegQueryValueEx* P <0T =U4 >U4 >0T =U4'
⎕NA'I4 advapi32 |RegSetValueEx* P <0T =U4 U4 <0T U4'
⎕NA'P dyalog32 |STRNCPY P P P'
⎕NA'P dyalog32 |STRNCPYA P P P'
⎕NA'P dyalog32 |STRNCPYW P P P'
⎕NA'P dyalog32 |MEMCPY P P P'
⎕NA'I4 gdi32 |AddFontResource* <0T'
⎕NA'I4 gdi32 |BitBlt P I4 I4 I4 I4 P I4 I4 U4'
⎕NA'U4 gdi32 |GetPixel P I4 I4'
⎕NA'P gdi32 |GetStockObject I4'
⎕NA'I4 gdi32 |RemoveFontResource* <0T'
⎕NA'U4 gdi32 |SetPixel P I4 I4 U4'
⎕NA' glu32 |gluPerspective F8 F8 F8 F8'
⎕NA'I4 kernel32 |CopyFile* <0T <0T I4'
⎕NA'P kernel32 |GetEnvironmentStrings'
⎕NA'U4 kernel32 |GetLastError'
⎕NA'U4 kernel32 |GetTempPath* U4 >0T'
⎕NA'P kernel32 |GetProcessHeap'
⎕NA'I4 kernel32 |GlobalMemoryStatusEx ={U4 U4 U8 U8 U8 U8 U8 U8}'
⎕NA'P kernel32 |HeapAlloc P U4 P'
⎕NA'I4 kernel32 |HeapFree P U4 P'
⎕NA' opengl32 |glClearColor F4 F4 F4 F4'
⎕NA' opengl32 |glClearDepth F8'
⎕NA' opengl32 |glEnable U4'
⎕NA' opengl32 |glMatrixMode U4'
⎕NA'I4 user32 |ClientToScreen P ={I4 I4}'
⎕NA'P user32 |FindWindow* <0T <0T'
⎕NA'I4 user32 |ShowWindow P I4'
⎕NA'I2 user32 |GetAsyncKeyState I4'
⎕NA'P user32 |GetDC P'
⎕NA'I4 User32 |GetDialogBaseUnits'
⎕NA'P user32 |GetFocus'
⎕NA'U4 user32 |GetSysColor I4'
⎕NA'I4 user32 |GetSystemMetrics I4'
⎕NA'I4 user32 |InvalidateRgn P P I4'
⎕NA'I4 user32 |MessageBox* P <0T <0T U4'
⎕NA'I4 user32 |ReleaseDC P P'
⎕NA'P user32 |SendMessage* P U4 P P'
⎕NA'P user32 |SetFocus P'
⎕NA'I4 user32 |WinHelp* P <0T U4 P'
⎕NA'I4 winnm |sndPlaySound <0T U4'