Tips and recommendations for user-defined functions

Why and how to create functions

How to receive and return values

Passing and returning strings, arrays, etc

Local variables

The help section

Miscellaneous tips and tricks

Using passwords

 

See also: user-defined functions, about functions, programming in QM

Why and how to create functions

An user-defined function is a macro that can be called from other macros. You create a function using menu File -> New -> New Function. You can place functions anywhere except in System folder. A function can be called from any macro/function/menu/toolbar. In QM, functions cannot be defined in code of other macros/functions (you must always create a separate item of type Function and place function's code there).

 

Sooner or later, you'll find that several your macros contain the same or similar code that performs the same operation. But you probably don't want to create or copy the same code again and again. You can create a function and place the code there. Then each macro could use the code by simply calling the function by name.

 

For example, you have two macros:

 

Macro1:

 

lpstr s1="c:\md\t.txt"
str s2
int i

 get filename extension
i=findcr(s1 '.')
if(i>=0) s2.get(s1 i)
else s2=""

out "%i %s" i s2

 

Macro2:

 

lpstr a="d:\mm\fav.mp3"
str b
int c

 get filename extension
c=findcr(a '.')
if(c>=0) b.get(a c)
else b=""

out "%i %s" c b

 

Both macros use the same code to get filename extension. Instead of using the same code in each macro, you can create a function and place the code there. Create new function using menu File -> New -> New Function, and give it some meaningful name, e.g. fileext.

 

Function fileext:

 

 /
function lpstr'f str&e

int i=findcr(f '.')
if(i>=0) e.get(f i)
else e=""
ret i

 

Now you can use the function in macros:

 

Macro1:

 

lpstr s1="c:\md\t.txt"
str s2
int i

i=fileext(s1 s2)

out "%i %s" i s2

 

Macro2:

 

lpstr a="d:\mm\fav.mp3"
str b
int c

c=fileext(a b)

out "%i %s" c b

 

Now macros are not only shorter, but also easier to read (the function name says what the function does). Also, if you ever want to enhance the code that gets filename extension, you don't have to do it in all macros.

 

Below is shown how the function is called and executed. Red lines - execution flow direction. Green lines - passing and returning values. The second argument is declared as reference (&), therefore the function receives address of variable e and can modify its value.

 

How to receive and return values

Here is an example of code at the beginning of a function:

 

 /
function# a str's [str&so]

 

The " /" at the beginning means that this function can be called from code, but cannot be launched to run like macro. It can be followed by the name of a macro. Then, when you press the Run button, the macro would run instead of this function. This also can be set in the Properties dialog.

 

The "function" statement defines function's arguments and return type. It does not include function's name.

 

"#" tells that this function returns an integer value. You could also use "'int" instead (see type declaration characters).

 

This function can receive three arguments - a, s and so. "[]" shows that third argument is optional.

 

Function arguments are normal local variables. They are created and initialized each time when function is called.

 

First argument is variable a. Its type is int (integer). You could use "int'a", or "#a", but type specification for int arguments is optional. When function is called, a receives copy of integer value specified by caller.

 

Second argument is variable s. The "str'" specifies that its type is str (string). Alternatively, you could use "~s". When the function is called, s receives copy of string value specified by caller.

 

Third argument is variable so. The "str&" specifies that it is reference to variable of type str. When function is called, so actually receives address of variable (caller must pass variable of type str). When the function modifies variable so, it actually modifies caller's variable. In other words, so is not true variable, but only alias of caller's variable. This method of passing arguments is called "by reference". It provides better performance, because string copying is eliminated. But even better, you can use this method to return one or more values. It is good alternative to the "ret" statement, which copies and returns single value.

 

The "function" statement is optional. Use it only if the function accepts arguments and/or returns something other than integer value.

 

The "ret" statement is used to exit function and continue to execute caller's code (caller waits while function runs). It can return some value, or, if value is not specified, just exit. The "ret" statement is optional. If function exits not through "ret", or "ret" does not return a value, function's return value is 0. See example below.

Passing and returning strings, arrays, etc

There are several ways to pass strings to functions. Each way has its advantages and disadvantages. Below are examples. For simplicity, the examples have only single argument, although any number, type and order of arguments could be used.

 

1. Function begins with

 

function lpstr's

 

(or function $s, because $ is shortcut for lpstr). Then you can pass any string. If you accidentally try to pass a number, you get error. I use this way in most my functions because it is fast and type-safe. If I need to use str functions with s, I assign s to a str variable inside the function. But if you don't know what is lpstr and how to use it, use way 2 or 3 instead. Although the function can modify the string, you should never do it (use way 3 instead), because the caller may pass a constant.

 

2. Function begins with

 

function str's

 

(or function ~s, because ~ is shortcut for str). Then you can pass any string or number. Numbers are automatically converted. Advantages: easiest, does not require knowledge about lpstr; s can be manipulated using str functions without at first storing to a str variable. Disadvantages: whole string is copied, which is slower and in case of large strings requires much memory; not error if you accidentally pass a number.

 

3. Function begins with

 

function str&s

 

(or function ~&s). Then you must pass a str variable. The function can modify the variable. Usually used to return string values (instead of using ret). An example is given above (function fileext). Advantages: fast and type-safe. Disadvantages: you cannot pass string constants (e.g. "some text") or variables of other than str type.

 

Also can be used function str*s, which is similar to 3 but requires knowledge about working with pointers.

 

To return strings, the function may begin with function'str or function'lpstr, and use ret. Although such functions are easier to use, but they are dangerous. You must understand variable scope, etc. Therefore, the preferred way to return string values is the way 3 described above.

 

Safe arrays (and not only str arrays) and user-defined types also usually are passed and returned like strings using the way 3, because it is fastest (does not copy whole array or structure) and safest. Example function:

 

function ARRAY(str)&a

 

Passing an interface pointer using 3 is slightly faster, although passing it using 2 usually also is quite fast and does not copy whole object.

Local variables

Variables that you declare in a function are local to the function (unless you declare them as global or thread). They are destroyed when the function exits. Even if the function is called multiple times, its variables do not retain values between calls. Local variables are visible (can be used) only in that function. Caller's variables are not visible in the function. Arguments (those in "function" statement) also are local variables.

 

Read more: variables, declaring variables, variable scope

The help section

Below the "function" statement, you can optionally write some comments about the function. The function statement and comments below the actual code (separated by at least one empty line) is function's help section. It is displayed in the Tips pane when you click the function name in code and press F1. The comments can be followed by EXAMPLE or EXAMPLES line and commented code showing how to call the function. The code will be colored in the Tips pane. See example below.

Miscellaneous tips and tricks


Below is an example code of a function. Actually it does nothing useful, but it includes some tricks that you should know.

 

 /
function# a str's [str&so]

 What it does ...
 Returns ...
 Error if ...
 ...

 a - ...
 s - ...
 so - ...

 EXAMPLE
 str s
 FunctionName 1 "abc" s
 out s


spe -1 ;;set speed to be equal to caller's speed (initially function's speed is 0). Of course, this is not useful in functions that don't have macro commands that are affected by spe.

if(a=0) end "invalid argument" ;;validate a: if a is 0, generate error. Error is generated in caller. If caller does not use "err" statement to handle it, macro ends.

if(getopt(nargs) < 3) ;;if third argument is omitted
	 ... (more code)
	ret ;;exit function and return 0

if(&so) ;;if so is valid (caller may pass 0, or omit third argument)
	so.from(s a) ;;modify the variable that was passed by reference

 ... (more code)

wait -2 ;;autodelay (wait number of milliseconds equal to caller' speed). You may consider to add it at the end of some functions.

ret 1 ;;exit function and return 1

err+ end _error ;;pass all errors generated in this function to the caller

 here ret is not necessary (the function will automatically return 0)

 

Using passwords

You should not pass passwords to nonsecure user-defined functions. You should use encrypted passwords. An user-defined function that accepts a password is secure only if it matches all these requirements:

 

1. Is encrypted.

 

2. Supports encrypted password (QM 2.1.8.5). That is, contains code like this:

 

truepassword.decrypt(16 encryptedpassword "encryptionkey")

 

Here truepassword is a str variable, encryptedpassword is the password argument, encryptionkey is an encryption key that should be unique to this function. Read more about str.decrypt and str.encrypt.

 

To encrypt passwords, can be used Options -> Security dialog or str.encrypt. When encrypting a password, QM extracts the encryption key from the function, and encrypts the password using the encryption key. An encrypted password has this format: [*XXXXXXXXXXXXXXXXXX*]. You can simply pass it to the function, like Function(a b "[*0123456789ABCDEF*]"), or embed it in a string, like Function(a b "user=Me;password=[*0123456789ABCDEF*];").

 

QM functions that accept password (net, AutoPassword, etc) support encrypted password.

 

3. Does not pass nonencrypted password to nonsecure user-defined functions.

 

4. Does not paste or type the password. Does not use anything that allows the password to be entered somewhere in visible form.

 

To enter a password into a password field of a certain program, use function AutoPassword. It is easiest and most secure way to enter a password from a macro. It cannot enter the password in a non-password-field.

 

To make your macro that uses passwords really secure, you should also encrypt it and use inpp to ask for password to run it. You can use the "Password input box" dialog for this.