# (c) 2021 Ashok P. Nadkarni
# See LICENSE for license terms.
#
# This file contains tests for cffi::Wrapper pertaining to definition and
# invocation of functions.

source [file join [file dirname [info script]] common.tcl]

namespace eval ${NS}::test {
    testDll function getEinvalString void {buf {chars[1000] out}}
    variable einvalMessage
    getEinvalString einvalMessage
}

# Function definition tests

namespace eval ${NS}::test {

    testnumargs function "testDll function" "NAME RETURNTYPE PARAMDEFS" ""

    test function-noargs-0 {Basic function definition} -cleanup {
        rename noargs {}
    } -body {
        testDll function noargs int {}
        noargs
    } -result 42

    test function-noargs-error-0 {Basic function definition - wrong number of args} -cleanup {
        rename noargs {}
    } -body {
        testDll function noargs int {}
        list [catch {noargs 0} result] $result $::errorCode
    } -result [list  1 {Syntax: noargs} {cffi ERROR {Syntax: noargs}}]

    test function-alias-0 {Function alias} -cleanup {
        rename noargs-alias {}
    } -body {
        testDll function {noargs noargs-alias} int {}
        list [noargs-alias] [catch {noargs} result] $result
    } -result [list 42 1 {invalid command name "noargs"}]

    test function-alias-error-0 {Function alias - wrong args} -cleanup {
        rename noargs-alias {}
    } -body {
        testDll function {noargs noargs-alias} int {}
        list [catch {noargs-alias 0} result] $result $::errorCode
    } -result [list  1 {Syntax: noargs-alias} {cffi ERROR {Syntax: noargs-alias}}]

    test function-alias-error-1 {Function alias - invalid alias} -body {
        list [catch {testDll function {noargs noargs-alias extra} int {}} result] $result $errorCode
    } -result [list 1 {Invalid value "noargs noargs-alias extra". Empty or invalid function name specification.} {cffi INVALID_VALUE {Invalid value "noargs noargs-alias extra". Empty or invalid function name specification.}}]

    test function-multiargs-0 {Multiple parameter function} -cleanup {
        rename twoargs {}
    } -body {
        testDll function twoargs int {a int b int}
        twoargs 1 2
    } -result 3

    test function-multiargs-error-0 {Multiple parameter function - no args} -cleanup {
        rename twoargs {}
    } -body {
        testDll function twoargs int {a int b int}
        list [catch twoargs result] $result $::errorCode
    } -result [list  1 {Syntax: twoargs a b} {cffi ERROR {Syntax: twoargs a b}}]

    test function-multiargs-error-1 {Multiple parameter function - one args} -cleanup {
        rename twoargs {}
    } -body {
        testDll function twoargs int {a int b int}
        list [catch {twoargs 0} result] $result $::errorCode
    } -result [list  1 {Syntax: twoargs a b} {cffi ERROR {Syntax: twoargs a b}}]

    test function-multiargs-error-2 {Multiple parameter function - extra args} -cleanup {
        rename twoargs {}
    } -body {
        testDll function twoargs int {a int b int}
        list [catch {twoargs 0 0 0} result] $result $::errorCode
    } -result [list  1 {Syntax: twoargs a b} {cffi ERROR {Syntax: twoargs a b}}]

    test function-namespace-0 {Function in global namespace} -cleanup {
        rename ::noargs {}
    } -body {
        uplevel #0 [list ${NS}::test::testDll function noargs int {}]
        list [info commands ::noargs] [::noargs]
    } -result [list ::noargs 42]

    test function-namespace-1 {Function in explicit namespace} -cleanup {
        namespace delete ${NS}::test::temp
    } -body {
        namespace eval ${NS}::test::temp [list ${NS}::test::testDll function noargs int {}]
        ${NS}::test::temp::noargs
    } -result 42

    test function-namespace-2 {Function alias in explicit namespace} -cleanup {
        namespace delete ${NS}::test::temp
    } -body {
        namespace eval ${NS}::test::temp [list ${NS}::test::testDll function {noargs noargs-alias} int {}]
        ${NS}::test::temp::noargs-alias
    } -result 42

    test function-error-0 {Missing function} -body {
        list [catch {testDll function nosuchfunction int {}} result] $result $::errorCode
    } -result {1 {Symbol "nosuchfunction" not found or inaccessible. } {cffi NOT_FOUND {Symbol "nosuchfunction" not found or inaccessible. }}}

    test stdcall-0 {stdcall} -body {

        if {$::tcl_platform(platform) eq "windows" && $::tcl_platform(pointerSize) == 4} {
            # VC++ has _, mingw does not
            set fn _stdcalltest@16
            if {[catch {testDll stdcall $fn double {a double b double}}]} {
                set fn stdcalltest@16
                testDll stdcall $fn double {a double b double}
            }
        } else {
            set fn stdcalltest
            testDll stdcall stdcalltest double {a double b double}
        }
        $fn 1 2
    } -result 0.5

    test functions-0 {Multiple functions} -cleanup {
        rename onearg {}
        rename twoargs-alias {}
    } -body {
        testDll functions {
            onearg int {a int}
            {twoargs twoargs-alias} int {a int b int}
        }
        list [onearg 2] [twoargs-alias 1 2] [catch {twoargs 1 2}] [catch {uplevel #0 onearg 0}]
    } -result {-2 3 1 1}

    test functions-1 {Multiple functions in namespace} -setup {
    } -cleanup {
        namespace delete ${NS}::test::temp
    } -body {
        namespace eval ${NS}::test::temp {
            ${NS}::test::testDll functions {
                onearg int {a int}
                {twoargs twoargs-alias} int {a int b int}
            }
        }
        list [${NS}::test::temp::onearg 2] [${NS}::test::temp::twoargs-alias 1 2]
    } -result {-2 3}

    test functions-2 {Multiple functions - empty} -body {
        testDll functions {}
    } -result {}

    test functions-error-0 {Multiple functions - invalid count} -body {
        testDll functions {onearg}
    } -result {Invalid value "onearg". Incomplete function definition list.} -returnCodes error

    test stdcalls-0 {Multiple stdcalls} -cleanup {
        rename $fn {}
        rename stdcall-alias {}
    } -body {
        if {$::tcl_platform(platform) eq "windows" && $::tcl_platform(pointerSize) == 4} {
            set fn _stdcalltest@16
        } else {
            set fn stdcalltest
        }
        set fnlist [list \
                        $fn double {a double b double} \
                        [list $fn stdcall-alias] double {a double b double} \
                       ]
        if {[catch {testDll stdcalls $fnlist}] && $fn eq "_stdcalltest@16"} {
            # Mingw does not prefix _
            set fn stdcalltest@16
            set fnlist [list \
                            $fn double {a double b double} \
                            [list $fn stdcall-alias] double {a double b double} \
                           ]
            testDll stdcalls $fnlist
        }

        list [$fn 1.0 2] [stdcall-alias 1 4.0] [catch [list uplevel #0 $fn 1 2]]
    } -result {0.5 0.25 1}

    # Basic type conversion to test input parameters and return types

    foreach type [linsert $numericTypes end string] {
        # Define functions for number to number
        foreach to_type $numericTypes {
            testDll function ${type}_to_${to_type} $to_type [list param $type]
        }
        testDll function ${type}_to_void void [list param $type]
        test ${type}_to_void "void ($type)" -body {
            ${type}_to_void 0
        } -result ""
        foreach to_type $intTypes {
            test ${type}_to_${to_type}-0 "$to_type ($type = 0)" -body {
                ${type}_to_${to_type} 0
            } -result 0
        }
        foreach to_type $realTypes {
            test ${type}_to_${to_type}-0 "$to_type ($type = 0)" -body {
                ${type}_to_${to_type} 0
            } -result 0.0
        }
    }

    # NOTE: these also test "in" parameter types for pointer, chars, bytes, binary
    foreach to_type $intTypes {
        testDll function pointer_to_$to_type $to_type [list param {pointer}]
        testDll function [list pointer_to_$to_type pointer_to_$to_type] $to_type [list param {pointer unsafe}]
        testDll function [list pointer_to_$to_type pointer_nullok_unsafe_to_$to_type] $to_type [list param {pointer unsafe nullok}]
        testDll function [list string_to_$to_type chars_to_$to_type] $to_type [list param {chars[20]}]
        testDll function [list string_to_$to_type bytes_to_$to_type] $to_type [list param {bytes[20]}]
        testDll function [list string_to_$to_type binary_to_$to_type] $to_type [list param binary]

        test pointer_to_${to_type}-0 "$to_type (safe pointer = 1)" -body {
            pointer_to_${to_type} 1^
        } -result 1
        test pointer_nullok_unsafe_to_${to_type}-0 "$to_type (unsafe pointer = 0)" -body {
            pointer_nullok_unsafe_to_${to_type} NULL
        } -result 0

        test chars_to_${to_type}-0 "$to_type (chars = 123)" -body {
            chars_to_${to_type} 123
        } -result 123

        test bytes_to_${to_type}-0 "$to_type (bytes = 123)" -body {
            bytes_to_${to_type} 123\0
        } -result 123

        test binary_to_${to_type}-0 "$to_type (bytes = 123)" -body {
            binary_to_${to_type} 123\0
        } -result 123
    }


    ###
    # Parameter tests

    ## Parameter tests - Numeric types
    foreach type $numericTypes {
        set val [expr {$type in {float double} ? "1.0" : "1"}]

        test function-param-in-$type-0 "$type in params" -setup {
            set fn ${type}_to_${type}
            testDll function $fn $type [list param [list $type in]]
        } -body {
            $fn $val
        } -result $val

        set fn ${type}_byref
        testDll function $fn $type [list inparam [list $type byref]]
        test function-param-byref-$type-0 "$type byref params" -body {
            $fn $val
        } -result [expr {2+$val}]
        testDll function $fn $type [list inparam [list $type in byref]]
        test function-param-byref-$type-1 "$type in byref params" -body {
            $fn $val
        } -result [expr {2+$val}]

        set fn ${type}_out
        testDll function $fn $type [list inparam [list $type in] outparam [list $type out]]
        test function-param-out-$type-0 "$type out params" -body {
            unset -nocomplain out
            list [$fn $val out] $out
        } -result [list [expr {2+$val}] [expr {1+$val}]]
        test function-param-out-$type-1 "$type out params - existing var" -body {
            set out 99
            list [$fn $val out] $out
        } -result [list [expr {2+$val}] [expr {1+$val}]]
        test function-paramout-$type-2 "$type out params - existing array var" -cleanup {
            unset arr
        } -body {
            set arr(x) x
            list [$fn $val arr] $arr
        } -result [list [expr {2+$val}] [expr {1+$val}]]

        test function-param-inout-$type-0 "$type inout params" -setup {
            set fn ${type}_inout
            testDll function $fn $type [list inoutparam [list $type inout]]
        } -body {
            set param $val
            list [$fn param] $param
        } -result [list [expr {2+$val}] [expr {1+$val}]]

        if {$type in $intTypes} {
            set fn ${type}_out
            set rettype $type
        } else {
            set fn ${type}_retval
            set rettype int
        }
        test function-param-retval-$type-0 "$type retval params" -setup {
            testDll function $fn [list $rettype nonzero] [list inparam [list $type in] outparam [list $type retval]]
        } -body {
            $fn $val
        } -result [expr {1+$val}]

        test function-param-retval-$type-error-0 "$type multiple retval"  -setup {
            set fn ${type}_retval
        } -body {
            # Prototype does not match C function but no matter. Not actually called
            testDll function $fn [list int zero] [list inparam [list $type retval] outparam [list $type retval]]
        } -result {The "retval" annotation must not be placed on more than one parameter definition.* Error defining function *} -match glob -returnCodes error

        test function-param-retval-$type-error-1 "$type retval - disallowed function decl" -setup {
            set fn ${type}_retval
        } -body {
            testDll function $fn int [list inparam [list $type in] outparam [list $type retval]]
        } -result {The "retval" annotation can only be used in a parameter definition in functions with integer return types with error checking annotations.* Error defining function *} -match glob -returnCodes error

        test function-param-retval-$type-error-2 "$type retval in" -setup {
            set fn ${type}_retval
        } -body {
            testDll function $fn int [list inparam [list $type in retval] outparam [list $type]]
        } -result {Invalid value*Unknown, repeated or conflicting type annotations specified.* Error defining function *} -match glob -returnCodes error -match glob

        test function-param-retval-$type-error-3 "$type retval inout" -setup {
            set fn ${type}_retval
        } -body {
            testDll function $fn int [list inparam [list $type inout retval] outparam [list $type]]
        } -result {Invalid value*Unknown, repeated or conflicting type annotations specified.* Error defining function *} -match glob -returnCodes error -match glob

        test function-param-retval-$type-error-4 "$type retval - syntax too many args" -setup {
            set fn ${type}_retval
        } -body {
            testDll function $fn {int zero} [list inparam [list $type in] outparam [list $type out retval]]
            $fn 2 x
        } -result "Syntax: ${type}_retval inparam" -returnCodes error

        test function-param-retval-$type-error-5 "$type retval - syntax too few args" -setup {
            set fn ${type}_retval
        } -body {
            testDll function $fn {int zero} [list inparam [list $type in] outparam [list $type out retval]]
            $fn
        } -result "Syntax: ${type}_retval inparam" -returnCodes error

        test function-param-retval-$type-error-6 "$type retval - error return" -setup {
            set fn ${type}_retval
        } -body {
            testDll function $fn {int zero} [list inparam [list $type in] outparam [list $type out retval]]
            $fn 1
        } -result {Invalid value "1". Function returned an error value.} -returnCodes error


        foreach attr [linsert $pointerAttrs end nullifempty] {
            test function-param-$type-$attr-error-0 "$type invalid attribute" -body {
                testDll function ${type}_to_void void [list param [list $type $attr]]
            } -result "Invalid value \"$type $attr\". $errorMessages(attrtype)* Error defining function *" -match glob -returnCodes error
        }

        # Error handler attrs to be ignored
        if {$type in $intTypes} {
            test function-param-$type-errno-0 "$type in params errno" -setup {
                set fn ${type}_to_${type}
                testDll function $fn $type [list param [list $type in errno]]
            } -body {
                $fn $val
            } -result $val

            test function-param-$type-onerror-0 "$type out params onerror" -body {
                set fn ${type}_out
                testDll function $fn $type [list inparam [list $type in {onerror handler}] outparam [list $type out {onerror handler}]]
                unset -nocomplain out
                list [$fn $val out] $out
            } -result [list [expr {2+$val}] [expr {1+$val}]]

            test function-param-$type-lasterror-0 "$type in params lasterror" -constraints win -setup {
                set fn ${type}_to_${type}
                testDll function $fn $type [list param [list $type in lasterror]]
            } -body {
                $fn $val
            } -result $val

            test function-param-$type-winerror-0 "$type in params winerror" -constraints win -setup {
                set fn ${type}_to_${type}
                testDll function $fn $type [list param [list $type in winerror]]
            } -body {
                $fn $val
            } -result $val
        }

        # Parameters do not permit requirements
        foreach attr $requirementAttrs {
            if {$type in $intTypes} {
                set msg $errorMessages(attrparsemode)
            } else {
                set msg $errorMessages(attrtype)
            }
            test function-param-$type-$attr "$type invalid attribute" -body {
                testDll function ${type}_to_void void [list param [list $type $attr]]
            } -result "Invalid value \"$type $attr\". $msg* Error defining function *" -match glob -returnCodes error
        }
    }

    # Limit tests for integer types
    foreach type $intTypes {
        # Input param and return value
        # Need to test in both string and numeric form.
        set max $intMax($type)
        set min $intMin($type)
        set maxincr [expr {$max+1}]
        set maxdecr [expr {$max-1}]
        set minincr [expr {$min+1}]
        set mindecr [expr {$min-1}]
        set goodnumbers [list $max $min $maxdecr $minincr]
        set badnumbers [list $maxincr $mindecr]

        foreach type2 [array names intMax] {
            if {$intMax($type2) > $intMax($type)} {
                lappend badnumbers $intMax($type2)
            }
            if {$intMin($type2) < $intMin($type)} {
                lappend badnumbers $intMin($type2)
            }
        }
        set goodandbad [concat $goodnumbers $badnumbers]

        set fn ${type}_to_$type
        testDll function $fn $type [list inparam $type]

        set testnum -1
        foreach good $goodnumbers {
            test function-$type-limit-number-[incr testnum] "$type = $good (number)" -body {
                $fn [incr good 0]
            } -result $good
            test function-$type-limit-string-$testnum "$type = $good (string)" -body {
                $fn [format %lld $good]
            } -result [format %lld $good]
            test function-$type-limit-hexstring-$testnum "$type = $good (hex string)" -body {
                $fn [if {$good >= 0} {format 0x%llx $good} else {format -0x%llx [expr {- $good}]}]
            } -result [format %lld $good]
        }

        set testnum -1
        foreach bad $badnumbers {
            test function-$type-limit-number-error-[incr testnum] "$type = $bad error (number)" -body {
                $fn $bad
            } -result "not in range|too large" -returnCodes error -match regexp
            test function-$type-limit-string-error-$testnum "$type = $bad error (string)" -body {
                $fn [format %lld $bad]
            } -result "not in range|too large" -returnCodes error -match regexp
            test function-$type-limit-hexstring-error-$testnum "$type = $bad error (hex string)" -body {
                $fn [if {$bad >= 0} {format 0x%llx $bad} else {format -0x%llx [expr {- $bad}]}]
            } -result "not in range|too large" -returnCodes error -match regexp
        }

        #
        # Output parameter
        set fn ${type}_out
        testDll function $fn $type [list inparam [list $type in] outparam [list $type out]]
        set val $intMax($type)
        incr val -2
        test function-$type-limit-2 "$type range limit" -body {
            unset -nocomplain out
            list [$fn $val out] $out
        } -result [list [expr {2+$val}] [expr {1+$val}]]

    }

    ## Parameter tests - string, unistring
    foreach {type val} [list string $testStrings(ascii) unistring $testStrings(unicode)] {
        set len [string length $val]
        set repeat 100000
        set largelen [expr {[string length $val] * $repeat}]
        set largeval [string repeat $val $repeat]

        set fn ${type}_len
        testDll function $fn int [list input $type]
        test function-$type-in-0 "$type in" -body {
            $fn $val
        } -result $len
        test function-$type-in-1 "$type in large" -body {
            $fn $largeval
        } -result $largelen

        set fn ${type}_inbyref_len
        testDll function $fn int [list input [list $type in byref]]
        test function-$type-in-byref-0 "$type in byref" -body {
            $fn $val
        } -result $len
        test function-$type-in-byref-1 "$type in byref large" -body {
            $fn $largeval
        } -result $largelen

        set fn ${type}_inbyref_len
        testDll function $fn int [list input [list $type byref]]
        test function-$type-byref-0 "$type byref" -body {
            $fn $val
        } -result $len
        test function-$type-byref-1 "$type in byref large" -body {
            $fn $largeval
        } -result $largelen

        test function-$type-out-0 "$type out" -setup {
            set fn ${type}_param_out
            testDll function $fn int [list outvar [list $type out]]
        } -body {
            list [$fn outvar] $outvar
        } -result [list [string length $val] $val]
        test function-$type-out-byref-0 "$type out byref" -setup {
            set fn ${type}_param_out
            testDll function $fn int [list outvar [list $type out byref]]
        } -body {
            list [$fn outvar] $outvar
        } -result [list [string length $val] $val]

        test function-$type-retval-0 "$type retval" -setup {
            set fn ${type}_param_out
            testDll function $fn {int nonzero} [list outvar [list $type retval]]
        } -body {
            $fn
        } -result $val
        test function-$type-retval-error-0 "$type retval - error return" -setup {
            set fn ${type}_param_out
            testDll function $fn {int zero} [list outvar [list $type retval]]
        } -body {
            $fn
        } -result "Invalid value \"[string length $val]\". Function returned an error value." -returnCodes error

        set fn ${type}_reverse_inout
        test function-$type-inout-error-0 "$type inout" -body {
            testDll function $fn int [list param [list $type inout]]
        } -result "Invalid value \"$type inout\". A type annotation is not valid for the data type.* Error defining function *" -match glob -returnCodes error

        foreach attr $pointerAttrs {
            if {$attr ne "nullok"} {
                test function-param-$type-$attr-error-0 "$type invalid attribute" -body {
                    testDll function ${type}_to_void void [list param [list $type $attr]]
                } -result "Invalid value \"$type $attr\". $errorMessages(attrtype)* Error defining function *" -match glob -returnCodes error
            }
        }

        foreach attr [lsearch -all -inline -exact -not $requirementAttrs nonzero] {
            set msg $errorMessages(attrtype)
            test function-param-$type-$attr "$type invalid attribute" -body {
                testDll function ${type}_to_void void [list param [list $type $attr]]
            } -result "Invalid value \"$type $attr\". $msg* Error defining function *" -match glob -returnCodes error
        }

        test function-$type-nullifempty-0 "$type in nullifempty" -setup {
            testDll function pointer_to_pointer {pointer nullok unsafe} {s {string nullok nullifempty}}
        } -body {
            pointer_to_pointer ""
        } -result [makeptr 0]
        test function-$type-nullifempty-1 "$type in nullifempty non-empty" -setup {
            testDll function ${type}_len int [list param [list $type nullifempty]]
        } -body {
            ${type}_len abc
        } -result 3
        test function-$type-nullifempty-2 "$type struct field nullifempty" -setup {
            cffi::Struct create S { s {string nullifempty}}
            testDll function {extractStructWithPointer fn} {pointer nullok} {s {struct.S byref}}
        } -cleanup {
            rename fn {}
            S destroy
        } -body {
            fn {s {}}
        } -result [makeptr 0]
        test function-$type-nullifempty-3 "$type struct field nullifempty - non-empty" -setup {
            cffi::Struct create S { s {string nullifempty}}
            testDll function {extractStructWithPointer fn} {pointer unsafe nullok} {s {struct.S byref}}
        } -cleanup {
            rename fn {}
            S destroy
        } -body {
            string equal [fn {s {abc}}] [makeptr 0]
        } -result 0
    }


    # string.encoding
    # Note: do not include ascii for jis0208 encoding tests.
    # See https://core.tcl-lang.org/tcl/tktview/9bad909c6ed4691bcad1886d710a923791a524a2
    set val $testStrings(jis0208)
    set bufsize 100000
    testDll function jis0208_in int [list x string.jis0208]
    test function-string-encoding-in-0 "string encoding in" -body {
        jis0208_in $testStrings(jis0208)
    } -result 1
    test function-string-encoding-in-1 "string encoding in large" -body {
        jis0208_in [string repeat $testStrings(jis0208) [expr {$bufsize/2 - 1}]]
    } -result [expr {$bufsize/2 - 1}]
    test function-string-encoding-in-error-1 "string bad encoding" -body {
        testDll function jis0208_in int [list x string.nosuchencoding]
    } -result {unknown encoding "nosuchencoding"* Error defining function *} -match glob -returnCodes error

    # string array parameter tests
    test function-string-array-in-0 "string input array" -body {
        testDll function string_array_in string {strings string[3] n int}
        string_array_in {abc def ghi} 1
    } -result def

    test function-unistring-array-in-0 "unistring input array" -body {
        testDll function unistring_array_in unistring {strings unistring[3] n int}
        string_array_in [list abc $testStrings(unicode) ghi] 1
    } -result $testStrings(unicode)

    test function-string-array-out-0 "string output array" -setup {
        unset -nocomplain s
    } -body {
        testDll function string_array_out int {strings {string[n] out} n int}
        string_array_out s 4
        set s
    } -result {abc def ghi abc}

    test function-string-array-out-1 "string output array - zero size array" -setup {
        unset -nocomplain s
    } -body {
        testDll function string_array_out int {strings {string[n] out nullok} n int}
        string_array_out s 0
        array size s
    } -result 0

    test function-string-array-out-2 "string output array - uninitialized output" -setup {
        unset -nocomplain s
    } -body {
        testDll function string_array_out int {strings {string[3] out nullok} n int}
        string_array_out s 0
        set s
    } -result {{} {} {}}

    test function-string-array-retval-0 "string retval array" -body {
        testDll function string_array_out {int nonzero} {strings {string[n] retval} n int}
        string_array_out 4
    } -result {abc def ghi abc}

    test function-string-array-retval-1 "string retval array - zero size array" -body {
        testDll function string_array_out {int zero} {strings {string[n] retval nullok} n int}
        string_array_out 0
    } -result {}

    test function-string-array-retval-error-0 "string retval array - error" -body {
        testDll function string_array_out {int zero} {strings {string[n] retval} n int}
        string_array_out 4
    } -result {Invalid value "*". Function returned an error value.} -returnCodes error -match glob

    test function-unistring-array-out-0 "unistring output array" -setup {
        unset -nocomplain s
    } -body {
        testDll function unistring_array_out int {strings {unistring[n] out} n int}
        unistring_array_out s 2
        set s
    } -result [list $testStrings(unicode) "\u00e3\u00e4\u00e5"]

    test function-unistring-array-out-1 "unistring output array - zero size array" -setup {
        unset -nocomplain s
    } -body {
        testDll function unistring_array_out int {strings {unistring[n] out nullok} n int}
        unistring_array_out s 0
        array size s
    } -result 0

    test function-unistring-array-out-2 "unistring output array - uninitialized output" -setup {
        unset -nocomplain s
    } -body {
        testDll function unistring_array_out int {strings {unistring[3] out nullok} n int}
        unistring_array_out s 0
        set s
    } -result {{} {} {}}

    test function-unistring-array-retval-0 "unistring retval array" -body {
        testDll function unistring_array_out {int nonzero} {strings {unistring[n] retval} n int}
        unistring_array_out 2
    } -result [list $testStrings(unicode) "\u00e3\u00e4\u00e5"]

    test function-unistring-array-retval-1 "unistring retval array - zero size array" -body {
        testDll function unistring_array_out {int zero} {strings {unistring[n] retval nullok} n int}
        unistring_array_out 0
    } -result {}

    test function-unistring-array-retval-error-0 "unistring retval array" -body {
        testDll function unistring_array_out {int zero} {strings {unistring[n] retval} n int}
        unistring_array_out 2
    } -result {Invalid value "*". Function returned an error value.} -returnCodes error -match glob

    test function-string-array-inout-0 "string inout array" -setup {
        unset -nocomplain s
    } -body {
        testDll function string_array_out void {strings {string[3] inout} n int}
    } -result {Invalid value "string\[3\] inout". A type annotation is not valid for the data type.* Error defining function *} -match glob -returnCodes error
    test function-unistring-array-inout-0 "unistring inout array" -setup {
        unset -nocomplain s
    } -body {
        testDll function unistring_array_out void {strings {unistring[3] inout} n int}
    } -result {Invalid value "unistring\[3\] inout". A type annotation is not valid for the data type.* Error defining function *} -match glob -returnCodes error


    ## Parameter tests - chars, unichars
    # NOTE:  use only ascii for utf8 test here because of the buflen calculation
    foreach {type val} [list chars $testStrings(ascii) chars.utf-8 abc unichars $testStrings(unicode)] {
        set len [string length $val]
        set buflen [expr {$len+1}]
        set repeat 100000
        set largelen [expr {[string length $val] * $repeat}]
        set largebuflen [expr {$largelen+1}]
        set largeval [string repeat $val $repeat]
        if {$type in {chars chars.utf-8}} {
            set fn string_out
        } else {
            set fn unistring_out
        }
        testDll function $fn int [list input "$type\[$buflen\]" output [list $type\[$buflen\] out]]
        test function-$type-out-0 "$type out" -body {
            list [$fn $val result] $result
        } -result [list $len $val]
        test function-$type-out-1 "$type out empty string" -body {
            list [$fn "" result] $result
        } -result {0 {}}
        testDll function $fn int [list input "$type\[$largebuflen\]" output [list $type\[$largebuflen\] out]]
        test function-$type-out-2 "$type out" -body {
            list [$fn $largeval result] $result
        } -result [list $largelen $largeval]
        test function-$type-out-3 "$type out - uninitialized output" -setup {
            testDll function pointer_noop void [list s [list $type\[4\] out]]
        } -body {
            # This test attempts to check that the library guards against the called
            # function not initializing the output buffer, for example in case of
            # errors. This is not fool proof as there is no way to control existing
            # output buffer contents before calling function (inout is not the same case)
            # We expect the output buffer to be init'ed to leading \0
            pointer_noop output
            set output
        } -result ""

        test function-$type-retval-0 "$type retval" -setup {
            testDll function $fn {int nonzero} [list input "$type\[$buflen\]" output [list $type\[$buflen\] retval]]
        } -body {
            $fn $val
        } -result $val

        if {$type in {chars chars.utf-8}} {
            set fn string_reverse_inout
        } else {
            set fn unistring_reverse_inout
        }
        testDll function $fn int [list param [list $type\[$buflen\] inout]]
        test function-$type-inout-0 "$type inout" -body {
            set result $val
            list [$fn result] $result
        } -result [list $len [string reverse $val]]
        test function-$type-inout-1 "$type inout empty string" -body {
            set result ""
            list [$fn result] $result
        } -result {0 {}}
        test function-$type-inout-error-0 "$type inout" -body {
            set result "abcd"
            $fn result
        } -result "Invalid value*length is greater than specified maximum buffer size." -returnCodes error -match glob
        testDll function $fn int [list param [list $type\[$largebuflen\] inout]]
        test function-$type-inout-2 "$type inout" -body {
            set result $largeval
            list [$fn result] $result
        } -result [list $largelen [string reverse $largeval]]

        if {$type in {chars chars.utf-8}} {
            set fn chars_to_void
        } else {
            set fn ${type}_to_void
        }
        foreach attr [linsert $pointerAttrs end nullifempty] {
            if {$attr eq "nullok"} continue; # Skip nullok from pointerAttrs
            test function-param-$type-$attr-error-0 "$type invalid attribute" -body {
                testDll function $fn void [list param [list $type\[2\] $attr]]
            } -result "Invalid value \"{$type\\\[2\\\]} $attr\". $errorMessages(attrtype)* Error defining function *" -match glob -returnCodes error
        }

        foreach attr $errorAttrs {
            if {$type in $intTypes} {
                set msg $errorMessages(attrparsemode)
            } else {
                set msg $errorMessages(attrtype)
            }
            test function-param-$type-$attr "$type invalid attribute" -body {
                testDll function $fn void [list param [list $type\[2\] $attr]]
            } -result "Invalid value \"{$type\\\[2\\\]} $attr\". $msg* Error defining function *" -match glob -returnCodes error
        }
    }

    # chars.encoding
    set val $testStrings(jis0208)
    set bufsize 100000
    testDll function jis0208_in int [list x chars.jis0208\[$bufsize\]]
    test function-chars-encoding-in-0 "chars encoding in" -body {
        jis0208_in $testStrings(jis0208)
    } -result 1
    test function-chars-encoding-in-1 "chars encoding in large" -body {
        jis0208_in [string repeat $testStrings(jis0208) [expr {$bufsize/2 - 1}]]
    } -result [expr {$bufsize/2 - 1}]
    test function-chars-encoding-in-error-0 "chars encoding in large overflow" -body {
        jis0208_in [string repeat $testStrings(jis0208) [expr {$bufsize/2}]]
    } -result "Invalid value*String length is greater than specified maximum buffer size." -returnCodes error -match glob
    test function-chars-encoding-in-error-1 "chars bad encoding" -body {
        testDll function jis0208_in int [list x chars.nosuchencoding\[10\]]
    } -result {unknown encoding "nosuchencoding"* Error defining function *} -match glob -returnCodes error

    testDll function jis0208_out void [list bufsize int buf [list chars.jis0208\[$bufsize\] out]]
    test function-chars-encoded-out-0 "encoded out" -body {
        jis0208_out 4 out
        set out
    } -result $val
    test function-chars-encoded-out-1 "encoded out large" -body {
        jis0208_out $bufsize out
        set out
    } -result [string repeat $val [expr {$bufsize/2 - 1}]]

    testDll function jis0208_inout void [list param [list chars.jis0208\[20\] inout]]
    test function-chars-encoded-inout-0 "encoded inout" -body {
        set param [string repeat $val 2]
        jis0208_inout param
        set param
    } -result [string repeat $val 4]

    ## Parameter tests - bytes

    set val $testStrings(bytes)
    set len [string length $val]
    set largeval [string repeat $val $repeat]
    set largelen [string length $largeval]
    test function-bytes-in-0 "bytes in dynamic count (count, array)" -setup {
        testDll function uchar_array_in uchar {n int buf bytes[n]}
    } -body {
        uchar_array_in $len $val
    } -result 6
    test function-bytes-in-1 "bytes in dynamic count (array, count)" -setup {
        testDll function uchar_array_count_in uchar {buf bytes[n] n int}
    } -body {
        uchar_array_count_in $val $len
    } -result 6
    test function-bytes-out-0 "bytes out" -setup {
        testDll function bytes_out int [list n int input bytes\[$len\] output [list bytes\[$len\] out]]
    } -body {
        bytes_out $len $val out
        set out
    } -result $val
    test function-bytes-out-1 "bytes out large" -setup {
        testDll function bytes_out int [list n int input bytes\[$largelen\] output [list bytes\[$largelen\] out]]
    } -body {
        bytes_out $largelen $largeval out
        set out
    } -result $largeval

    test function-bytes-retval-0 "bytes retval" -setup {
        testDll function bytes_out {int nonzero} [list n int input bytes\[$len\] output [list bytes\[$len\] retval]]
    } -body {
        bytes_out $len $val
    } -result $val

    test function-bytes-inout-0 "bytes inout" -setup {
        testDll function bytes_inout void [list n int output [list bytes\[$len\] inout]]
    } -body {
        set out $val
        bytes_inout $len out
        set out
    } -result [string reverse $val]
    test function-bytes-inout-1 "bytes inout large" -setup {
        testDll function bytes_inout void [list n int output [list bytes\[$largelen\] inout]]
    } -body {
        set out $largeval
        bytes_inout $largelen out
        set out
    } -result [string reverse $largeval]
    test function-bytes-inout-error-0 "bytes inout buffer size error" -setup {
        testDll function bytes_inout void [list n int output [list bytes\[1\] inout]]
    } -body {
        set out $val
        bytes_inout 1 out
    } -result "Invalid value. Byte array length is greater than specified maximum buffer size." -returnCodes error

    ## Parameter tests - binary
    # Note: binary in tests are tested earlier (converting to numerics)
    test function-binary-in-byref-0 "binary in byref" -body {
        testDll function binary_inbyref_len int {bin {binary in byref}}
        binary_inbyref_len abc\0
    } -result 3
    test function-binary-byref-0 "binary byref" -body {
        testDll function binary_inbyref_len int {bin {binary byref}}
        binary_inbyref_len abc\0
    } -result 3
    test function-binary-in-nullifempty-0 "binary nullifempty" -body {
        testDll function pointer_to_pointer {pointer nullok unsafe} {b {binary nullifempty}}
        pointer_to_pointer ""
    } -result [makeptr 0]
    test function-binary-out-error-0 "binary out" -body {
        testDll function bytes_out void [list n int input binary output [list binary out]]
    } -result {Invalid value "binary out". A type annotation is not valid for the data type.* Error defining function *} -match glob -returnCodes error
    test function-binary-inout-error-0 "binary inout" -body {
        testDll function bytes_inout void [list n int output [list binary inout]]
    } -result {Invalid value "binary inout". A type annotation is not valid for the data type.* Error defining function *} -match glob -returnCodes error

    ## Parameter tests - pointers
    test function-pointer-in-0 "pointer in" -setup {
        testDll function pointer_in int {p pointer}
        cffi::Struct create TempStruct {i int}
        set p [TempStruct allocate]
        TempStruct tonative $p [list i 42]
    } -cleanup {
        TempStruct free $p
        TempStruct destroy
    } -body {
        pointer_in $p
    } -result 42

    test function-pointer-out-0 "pointer out" -setup {
        testDll function pointer_out void {p {pointer out}}
    } -cleanup {
        cffi::pointer dispose $p
    } -body {
        pointer_out p
        list [cffi::pointer isvalid $p] [binary scan [cffi::memory tobinary $p 4] n val] $val
    } -result [list 1 1 99]

    test function-pointer-retval-0 "pointer retval" -setup {
        testDll function pointer_retval {int nonzero} {inp {pointer unsafe} p {pointer retval}}
    } -cleanup {
        cffi::pointer dispose $p
    } -body {
        set p [pointer_retval 1^]
        list [cffi::pointer isvalid $p] $p
    } -result [list 1 [makeptr 2]]

    test function-pointer-retval-1 "pointer retval unsafe" -setup {
        testDll function pointer_retval {int nonzero} {inp {pointer unsafe} p {pointer unsafe retval}}
    } -body {
        set p [pointer_retval 1^]
        list [cffi::pointer isvalid $p] $p
    } -result [list 0 [makeptr 2]]

    test function-pointer-retval-2 "pointer retval tagged" -setup {
        testDll function pointer_retval {int nonzero} {inp {pointer unsafe} p {pointer.TAG retval}}
    } -cleanup {
        cffi::pointer dispose $p
    } -body {
        set p [pointer_retval 1^]
        list [cffi::pointer isvalid $p] $p
    } -result [list 1 [scoped_ptr 2 TAG]]

    test function-pointer-inout-0 "pointer inout" -setup {
        testDll function pointer_incr void {p {pointer inout}}
    } -cleanup {
        cffi::memory free $p
    } -body {
        set p [cffi::memory allocate 4]
        set p2 $p
        pointer_incr p2
        set addr [cffi::pointer address $p]
        set addr2 [cffi::pointer address $p2]
        list [cffi::pointer dispose $p2] [expr {$addr+1 == $addr2}]
    } -result [list {} 1]

    test function-pointer-byref-0 "pointer byref" -setup {
        testDll function pointer_byref {pointer unsafe} {p {pointer byref}}
        set p [memory allocate 1]
    } -cleanup {
        cffi::memory free $p
    } -body {
        set p2 [pointer_byref $p]
        expr {$p == $p2}
    } -result 1

    # pointer param safety attributes
    test function-pointer-param-safety-0 "pointer safe to unsafe" -setup {
        cffi::pointer safe 1^
    } -cleanup {
        cffi::pointer dispose 1^
    } -body {
        testDll function pointer_noop void {p {pointer unsafe}}
        pointer_noop 1^
    } -result ""

    test function-pointer-param-safety-1 "pointer unsafe to safe" -body {
        testDll function pointer_noop void {p pointer}
        pointer_noop 1^
    } -result "Pointer [makeptr 1] is not registered." -returnCodes error

    test function-pointer-param-safety-2 "pointer unsafe to unsafe" -body {
        testDll function pointer_noop void {p {pointer unsafe}}
        pointer_noop 1^
    } -result ""

    test function-pointer-param-safety-3 "pointer unsafe to counted" -body {
        testDll function pointer_noop void {p {pointer counted}}
        pointer_noop 1^
    } -result "Pointer [makeptr 1] is not registered." -returnCodes error

    test function-pointer-param-safety-4 "pointer unsafe dispose" -body {
        testDll function pointer_noop void {p {pointer dispose}}
        pointer_noop 1^
    } -result "Pointer [makeptr 1] is not registered." -returnCodes error -match glob

    test function-pointer-param-safety-5 "pointer unsafe dispose conflict" -body {
        testDll function pointer_noop void {p {pointer dispose unsafe}}
    } -result "Invalid value \"pointer dispose unsafe\". $errorMessages(attrconflict)* Error defining function *" -match glob -returnCodes error

    test function-pointer-param-safety-6 "pointer unsafe counted conflict" -body {
        testDll function pointer_noop void {p {pointer counted unsafe}}
    } -result "Invalid value \"pointer counted unsafe\". $errorMessages(attrconflict)* Error defining function *" -match glob -returnCodes error

    test function-pointer-param-safety-7 "pointer counted" -setup {
        cffi::pointer counted 1^
    } -body {
        testDll function pointer_noop void {p {pointer counted}}
        pointer_noop 1^
        pointer_noop 1^; # Should not increase reference count when passed as parameter
        list [cffi::pointer isvalid 1^] [cffi::pointer dispose 1^] [cffi::pointer isvalid 1^]
    } -result {1 {} 0}

    test function-pointer-param-safety-8 "pointer safe dispose" -setup {
        cffi::pointer safe 1^
    } -body {
        testDll function pointer_noop void {p {pointer dispose}}
        list [pointer_noop 1^] [cffi::pointer isvalid 1^]
    } -result {{} 0}

    test function-pointer-param-safety-9 "pointer counted dispose" -setup {
        cffi::pointer counted 1^
        cffi::pointer counted 1^
    } -body {
        testDll function pointer_noop void {p {pointer dispose counted}}
        list [pointer_noop 1^] [cffi::pointer isvalid 1^] [pointer_noop 1^] [cffi::pointer isvalid 1^]
    } -result {{} 1 {} 0}

    test function-pointer-param-safety-10 "pointer safe dispose - after param checks" -setup {
        set p [makeptr 1]
        cffi::pointer safe $p
        testDll function pointer_add {pointer unsafe} {p {pointer dispose} n int}
    } -body {
        # Verifies that pointer is not disposed if parameter has an error
        list \
            [cffi::pointer isvalid $p] \
            [catch {pointer_add $p notanint}] \
            [cffi::pointer isvalid $p] \
            [catch {pointer_add $p 0}] \
            [cffi::pointer isvalid $p] 
    } -result {1 1 1 0 0}

    test function-pointer-param-safety-11 "pointer counted dispose - after param checks" -setup {
        set p [makeptr 1]
        cffi::pointer counted $p
        cffi::pointer counted $p
        testDll function pointer_add {pointer unsafe} {p {pointer dispose} n int}
    } -body {
        # Verifies that pointer is not disposed if parameter has an error
        list \
            [cffi::pointer isvalid $p] \
            [catch {pointer_add $p notanint}] \
            [cffi::pointer isvalid $p] \
            [catch {pointer_add $p 0}] \
            [cffi::pointer isvalid $p] \
            [catch {pointer_add $p 0}] \
            [cffi::pointer isvalid $p]
    } -result {1 1 1 0 1 0 0}

    test function-pointer-param-safety-12 "pointer unsafe disposeonsuccess conflict" -body {
        testDll function pointer_noop void {p {pointer disposeonsuccess unsafe}}
    } -result "Invalid value \"pointer disposeonsuccess unsafe\". $errorMessages(attrconflict)* Error defining function *" -match glob -returnCodes error

    test function-pointer-param-safety-12.1 "pointer dispose disposeonsuccess conflict" -body {
        testDll function pointer_noop void {p {pointer disposeonsuccess dispose}}
    } -result "Invalid value \"pointer disposeonsuccess dispose\". $errorMessages(attrconflict)* Error defining function *" -match glob -returnCodes error


    test function-pointer-param-safety-13 "verify dispose in effect on function call errors" -setup {
        set p [makeptr 1]
        cffi::pointer safe $p
        testDll function pointer_dispose {int nonzero} {p {pointer dispose} i int}
    } -body {
        list [cffi::pointer isvalid $p] [catch {pointer_dispose $p 0}] [cffi::pointer isvalid $p]
    } -result {1 1 0}

    test function-pointer-param-safety-14 "verify disposeonsuccess NOT in effect on function call errors" -setup {
        set p [makeptr 1]
        cffi::pointer safe $p
        testDll function pointer_dispose {int nonzero} {p {pointer disposeonsuccess} i int}
    } -cleanup {
        cffi::pointer dispose $p
    } -body {
        list [cffi::pointer isvalid $p] [catch {pointer_dispose $p 0}] [cffi::pointer isvalid $p]
    } -result {1 1 1}

    test function-pointer-param-safety-15 "verify disposeonsuccess in effect on function call success" -setup {
        set p [makeptr 1]
        cffi::pointer safe $p
        testDll function pointer_dispose {int nonzero} {p {pointer disposeonsuccess} i int}
    } -body {
        list [cffi::pointer isvalid $p] [catch {pointer_dispose $p 1}] [cffi::pointer isvalid $p]
    } -result {1 0 0}

    test function-pointer-param-safety-16 "verify disposeonsuccess in effect on function with no error annotation" -setup {
        set p [makeptr 1]
        cffi::pointer safe $p
        testDll function pointer_dispose int {p {pointer disposeonsuccess} i int}
    } -body {
        list [cffi::pointer isvalid $p] [catch {pointer_dispose $p 1}] [cffi::pointer isvalid $p]
    } -result {1 0 0}

    test function-pointer-param-in-null-0 "pass null pointer - fail" -body {
        testDll function pointer_to_pointer void {p {pointer unsafe}}
        pointer_to_pointer [makeptr 0]
    } -result {Invalid value. Pointer is NULL.} -returnCodes error

    test function-pointer-param-in-null-nullok-0 "pass null pointer nullok" -body {
        testDll function pointer_to_pointer {pointer unsafe nullok} {p {pointer unsafe nullok}}
        pointer_to_pointer [makeptr 0]
    } -result [makeptr 0]

    test function-pointer-param-out-null-0 "output parameter NULL pointer is allowed by default" -body {
        testDll function pointer_reflect void {inp {pointer nullok} outp {pointer out}}
        pointer_reflect [makeptr 0] x
        set x
    } -result [makeptr 0]

    # pointer param - ignore error handler attributes
    test function-pointer-param-in-errno-0 "errno attr in pointer param " -body {
        testDll function pointer_to_pointer {pointer unsafe} {p {pointer unsafe errno}}
        pointer_to_pointer [makeptr 1]
    } -result [makeptr 1]

    test function-pointer-param-out-onerror-0 "onerror attr in pointer param " -body {
        testDll function pointer_reflect void {inp {pointer unsafe} outp {pointer out unsafe {onerror handler}}}
        pointer_reflect [makeptr 1] x
        set x
    } -result [makeptr 1]

    test function-pointer-param-in-lasterror-0 {
        lasterror attr in pointer param 
    } -constraints win -body {
        testDll function pointer_to_pointer {pointer unsafe} {p {pointer unsafe lasterror}}
        pointer_to_pointer [makeptr 1]
    } -result [makeptr 1]


    # pointer tags
    test function-pointer-param-tag-0 "pointer tag safe" -setup {
        testDll function pointer_noop void {p pointer.::TAG}
        cffi::pointer safe 1^::TAG
    } -cleanup {
        cffi::pointer dispose 1^::TAG
    } -body {
        pointer_noop 1^::TAG
    } -result ""

    test function-pointer-param-tag-1 "pointer tag unsafe" -body {
        testDll function pointer_noop void {p {pointer.TAG unsafe}}
        pointer_noop 1^[namespace current]::TAG
    } -result ""

    test function-pointer-param-tag-2 "pointer tag safe - wrong type" -body {
        testDll function pointer_noop void {p pointer.TAG}
        pointer_noop 1^TAG2
    } -result {Value "1^TAG2" has the wrong type. Pointer type mismatch.} -returnCodes error

    test function-pointer-param-tag-3 "pointer tag unsafe - wrong type" -body {
        testDll function pointer_noop void {p {pointer.TAG unsafe}}
        pointer_noop 1^TAG2
    } -result {Value "1^TAG2" has the wrong type. Pointer type mismatch.} -returnCodes error

    test function-pointer-param-tag-4 "pointer tag counted" -setup {
        testDll function pointer_noop void {p {pointer.::TAG counted}}
        cffi::pointer counted 1^::TAG
    } -cleanup {
        cffi::pointer dispose 1^::TAG
    } -body {
        pointer_noop 1^::TAG
    } -result ""

    test function-pointer-param-tag-5 "pointer tag counted - wrong type" -body {
        testDll function pointer_noop void {p {pointer.TAG counted}}
        pointer_noop 1^TAG2
    } -result {Value "1^TAG2" has the wrong type. Pointer type mismatch.} -returnCodes error

    # tag dispose
    test function-pointer-param-tag-6 "pointer tag dispose" -setup {
        testDll function pointer_noop void {p {pointer.TAG dispose}}
        set tag [namespace current]::TAG
        cffi::pointer safe 1^$tag
    } -body {
        list [pointer_noop 1^$tag] [cffi::pointer isvalid 1^$tag]
    } -result [list {} 0]

    test function-pointer-param-tag-7 "pointer tag dispose - wrong type" -body {
        testDll function pointer_noop void {p {pointer.TAG dispose}}
        pointer_noop 1^TAG2
    } -result {Value "1^TAG2" has the wrong type. Pointer type mismatch.} -returnCodes error

    test function-pointer-param-tag-8 "pointer tag safe passed to untagged" -setup {
        testDll function pointer_noop void {p pointer}
        cffi::pointer safe 1^TAG
    } -cleanup {
        cffi::pointer dispose 1^TAG
    } -body {
        pointer_noop 1^TAG
    } -result ""

    test function-pointer-param-tag-9 "pointer untagged safe passed to tagged" -setup {
        testDll function pointer_noop void {p pointer.TAG}
        cffi::pointer safe 1^
    } -cleanup {
        cffi::pointer dispose 1^
    } -body {
        list [catch {pointer_noop 1^} result] $result
    } -result {1 {Value "1^" has the wrong type. Pointer type mismatch.}}

    test function-pointer-param-tag-10 "pointer tag unsafe passed to untagged" -setup {
        testDll function pointer_noop void {p {pointer unsafe}}
    } -body {
        pointer_noop 1^TAG
    } -result ""

    test function-pointer-param-tag-11 "pointer untagged unsafe passed to tagged" -setup {
        testDll function pointer_noop void {p {pointer.TAG unsafe}}
    } -body {
        list [catch {pointer_noop 1^} result] $result
    } -result {1 {Value "1^" has the wrong type. Pointer type mismatch.}}

    # Pointer param error attributes
    foreach attr [linsert $requirementAttrs end nullifempty] {
        if {$attr in {winerror lasterror}} {
            set constraints win
        } else {
            set constraints {}
        }
        if {$attr in {lasterror errno}} {
            set msg $errorMessages(attrparsemode)
        } else {
            set msg $errorMessages(attrtype)
        }
        test function-pointer-param-$attr-0 "pointer invalid attribute" -constraints $constraints -body {
            testDll function pointer_to_void void [list param [list pointer $attr]]
        } -result "Invalid value \"pointer $attr\". $msg* Error defining function *" -match glob -returnCodes error
    }

    ## Parameter - struct
    test function-struct-out-0 "Param struct out" -setup {
        testDll function getTestStruct int {s {struct.TestStruct out}}
    } -body {
        getTestStruct s
        checkTestStruct $s
    } -result [list ]
    test function-struct-out-1 "Param out - struct with strings" -constraints libffi -body {
        testDll function getStructWithStrings void {s {struct.StructWithStrings out}}
        getStructWithStrings s
        set s
    } -result [makeStructWithStrings]
    test function-struct-out-2 "Param out - struct with null strings" -constraints libffi -body {
        testDll function getStructWithNullStrings void {s {struct.StructWithStrings out}}
        getStructWithNullStrings s
        set s
    } -result {s {} utf8 {} jis {} uni {}}

    test function-struct-out-crash-bug#39 "Param struct out" -setup {
        testDll function getTestStruct int {s {struct.TestStruct out}}
        proc crash {} {
            # Output is a procedure-local that does not exist
            getTestStruct s
            checkTestStruct $s
        }
    } -body {
        crash
    } -result [list ]

    test function-struct-retval-0 "Param struct retval" -setup {
        testDll function getTestStruct {int nonzero} {s {struct.TestStruct retval}}
    } -body {
        checkTestStruct [getTestStruct]
    } -result [list ]

    test function-struct-in-0 "Param struct in" -constraints libffi -setup {
        cffi::Struct create ::S {c uchar ll longlong s short}
        testDll function structCheckByVal int {s struct.::S c uchar ll longlong s short}
    } -body {
        set ll 0x7fffffffffff
        structCheckByVal [list c 1 ll $ll s 2] 1 $ll 2
    } -result 1

    test function-struct-in-1 "Param in byref - struct with strings" -constraints libffi -body {
        testDll function checkStructWithStrings int {s {struct.StructWithStrings byref}}
        checkStructWithStrings [makeStructWithStrings]
    } -result 0
    test function-struct-in-2 "Param in - struct with strings" -constraints libffi -body {
        testDll function checkStructWithStringsByVal int {s struct.StructWithStrings}
        checkStructWithStringsByVal [makeStructWithStrings]
    } -result 0

    test function-struct-in-error-0 "Param struct by value - error" -constraints {
        dyncall
    } -body {
        testDll function getTestStruct int {s struct.TestStruct}
    } -result "Invalid value \"struct.TestStruct\". $errorMessages(structbyref)" -returnCodes error


    test function-struct-in-byref-0 "Param struct in byref" -setup {
        cffi::Struct create ::S {c uchar ll longlong s short}
        testDll function structCheck int {s {struct.::S byref} c uchar ll longlong s short}
    } -body {
        set ll 0x7fffffffffff
        structCheck [list c 1 ll $ll s 2] 1 $ll 2
    } -result 1
    test function-struct-in-byref-1 "Param struct in byref - null if empty" -setup {
        cffi::Struct create ::S {c uchar ll longlong s short}
        testDll function pointer_to_pointer {pointer unsafe nullok} {s {struct.::S byref in nullifempty}}
    } -body {
        pointer_to_pointer {}
    } -result [makeptr 0]
    test function-struct-in-byref-2 "Param struct in byref - null if empty, override" -setup {
        cffi::Struct create ::S {c uchar ll longlong s short}
        testDll function pointer_to_pointer {pointer unsafe} {s {struct.::S byref in nullifempty}}
    } -body {
        cffi::pointer isnull [pointer_to_pointer {c 0 ll 0 s 0}]
    } -result 0
    test function-struct-in-byref-3 "Pass struct with pointer array in and out" -setup {
        catch {struct_with_pointer_array destroy}
        cffi::Struct create struct_with_pointer_array {ptrs {pointer[3] counted}}
        testDll function struct_pointer_array_exchange void {from {struct.struct_with_pointer_array in byref} to {struct.struct_with_pointer_array out}}
        set from [list ptrs [make_counted_pointers 1 2 3]]
        unset -nocomplain to
    } -cleanup {
        purge_pointers
    } -body {
        struct_pointer_array_exchange $from to
        list $from $to
    } -result [list [list ptrs [make_unsafe_pointers 1 2 3]] [list ptrs [make_unsafe_pointers 1 2 3]]]

    test function-struct-in-byref-error-0 "Param struct in byref - empty dictionary" -setup {
        cffi::Struct create ::S {c uchar ll longlong s short}
        testDll function structCheck int {s {struct.::S byref} c uchar ll longlong s short}
    } -body {
        set ll 0x7fffffffffff
        structCheck [list ] 1 $ll 2
    } -result {Struct field "c" not found or inaccessible. Field missing in struct dictionary value. Error converting field ::S.c to a native value.} -returnCodes error
    test function-struct-in-byref-error-1 "Param struct in byref missing field" -setup {
        cffi::Struct create ::S {c uchar ll longlong s short}
        testDll function structCheck int {s {struct.::S byref} c uchar ll longlong s short}
    } -body {
        set ll 0x7fffffffffff
        structCheck [list c 1 ll $ll] 1 $ll 2
    } -result {Struct field "s" not found or inaccessible. Field missing in struct dictionary value. Error converting field ::S.s to a native value.} -returnCodes error
    test function-struct-in-byref-error-2 "Param struct in byref missing field - with nullempty" -setup {
        cffi::Struct create ::S {c uchar ll longlong s short}
        testDll function structCheck int {s {struct.::S byref nullifempty} c uchar ll longlong s short}
    } -body {
        set ll 0x7fffffffffff
        structCheck [list c 1 ll $ll] 1 $ll 2
    } -result {Struct field "s" not found or inaccessible. Field missing in struct dictionary value. Error converting field ::S.s to a native value.} -returnCodes error

    test function-struct-inout-0 "Pass struct inout" -setup {
        testDll function getTestStruct int {s {struct.TestStruct out}}
        testDll function incrTestStruct void {s {struct.TestStruct inout}}
    } -body {
        getTestStruct s
        incrTestStruct s
        # TBD - fix this result check for all platforms, including 32-bits
        set s
    } -result [expr {$::tcl_platform(wordSize) == 4 ? {c 1 i 5 shrt 9 ui 13 ushrt 17 l 21 uc 25 ul 29 c3 33 ll 49 unic 57 ull 89 b abc f 101.0 s {c 105} d 121.0} : {c 1 i 5 shrt 9 ui 13 ushrt 17 l 25 uc 33 ul 41 c3 49 ll 65 unic 73 ull 105 b qrs f 117.0 s {c 121} d 137.0}}] 
    test function-struct-inout-1 "Pass struct with pointer array inout" -setup {
        catch {struct_with_pointer_array destroy}
        cffi::Struct create struct_with_pointer_array {ptrs {pointer[3] counted}}
        testDll function struct_pointer_array_exchange void {a {struct.struct_with_pointer_array inout} b {struct.struct_with_pointer_array inout}}
    } -cleanup {
        purge_pointers
    } -body {
        set a [list ptrs [make_counted_pointers 1 2 3]] 
        set b [list ptrs [make_counted_pointers 4 5 6]] 
        struct_pointer_array_exchange a b
        list $a $b
    } -result [list [list ptrs [make_unsafe_pointers 4 5 6]] [list ptrs [make_unsafe_pointers 1 2 3]]]

    test function-struct-inout-error-0 "Param struct inout - empty dictionary" -setup {
        testDll function incrTestStruct void {s {struct.TestStruct inout}}
    } -body {
        set s {}
        incrTestStruct s
    } -result {Struct field "c" not found or inaccessible. Field missing in struct dictionary value. Error converting field ::TestStruct.c to a native value.} -returnCodes error

    test function-struct-array-0 "Pass in and out array of structs" -setup {
        catch {SimpleStruct destroy}
        cffi::Struct create SimpleStruct {c uchar i longlong s short}
        unset -nocomplain outstruct
    } -body {
        testDll function structArrayCopy void {from {struct.SimpleStruct[n]} to {struct.SimpleStruct[n] out} n int}
        structArrayCopy {{c 0 i 1 s 2} {c 3 i 4 s 5} {c 6 i 7 s 8}} outstruct 3
        set outstruct
    } -result {{c 0 i 1 s 2} {c 3 i 4 s 5} {c 6 i 7 s 8}}

    test function-struct-array-1 "Pass in and out short array of structs" -setup {
        catch {SimpleStruct destroy}
        cffi::Struct create SimpleStruct {c uchar i longlong s short}
        unset -nocomplain outstruct
    } -body {
        testDll function structArrayCopy void {from {struct.SimpleStruct[n]} to {struct.SimpleStruct[n] out} n int}
        structArrayCopy {{c 0 i 1 s 2} {c 3 i 4 s 5}} outstruct 3
        set outstruct
    } -result {{c 0 i 1 s 2} {c 3 i 4 s 5} {c 0 i 0 s 0}}
 
    test function-struct-array-2 "Pass inout short array of structs" -setup {
        catch {SimpleStruct destroy}
        cffi::Struct create SimpleStruct {c uchar i longlong s short}
        set a {{c 0 i 1 s 2} {c 3 i 4 s 5}}
        set b {{c 10 i 11 s 12} {c 13 i 14 s 15}}
    } -body {
        testDll function structArrayExchange void {from {struct.SimpleStruct[n] inout} to {struct.SimpleStruct[n] inout} n int}
        structArrayExchange a b 2
        list $a $b
    } -result {{{c 10 i 11 s 12} {c 13 i 14 s 15}} {{c 0 i 1 s 2} {c 3 i 4 s 5}}}

    test function-struct-array-3 "Pass in and out struct with nested array of structs" -setup {
        catch {SimpleStruct destroy}
        cffi::Struct create SimpleStruct {c uchar i longlong s short}
        catch {StructWithNestedStructArray destroy}
        cffi::Struct create StructWithNestedStructArray {s struct.SimpleStruct[3] p {pointer[3] unsafe}}
        unset -nocomplain outstruct
    } -body {
        testDll function nestedStructArrayCopy void {from {struct.StructWithNestedStructArray byref} to {struct.StructWithNestedStructArray out}}
        nestedStructArrayCopy {s {{c 0 i 1 s 2} {c 3 i 4 s 5} {c 6 i 7 s 8}} p {1^ 2^ 3^}} outstruct
        set outstruct
    } -result [list s {{c 0 i 1 s 2} {c 3 i 4 s 5} {c 6 i 7 s 8}} p [make_unsafe_pointers 1 2 3]]

    test function-struct-array-error-0 "Pass in empty struct" -setup {
        catch {SimpleStruct destroy}
        cffi::Struct create SimpleStruct {c uchar i longlong s short}
        unset -nocomplain outstruct
    } -body {
        testDll function structArrayCopy void {from {struct.SimpleStruct[n]} to {struct.SimpleStruct[n] out} n int}
        structArrayCopy {{}} outstruct 3
        set outstruct
    } -result {Struct field "c" not found or inaccessible. Field missing in struct dictionary value. Error converting field ::cffi::test::SimpleStruct.c to a native value.} -returnCodes error

    # Tests for null values in pointer fields
    test function-struct-pointerfield-in-0 "input param, non-NULL" -body {
        catch {S destroy}
        cffi::Struct create S {p {pointer unsafe}}
        testDll function extractStructWithPointer {pointer unsafe nullok} {ins {struct.S byref in}}
        extractStructWithPointer [list p [makeptr 1]]
    } -result [makeptr 1]
    test function-struct-pointerfield-in-1 "input param, NULL" -body {
        catch {S destroy}
        cffi::Struct create S {p {pointer unsafe}}
        testDll function extractStructWithPointer {pointer unsafe nullok} {ins {struct.S byref in}}
        extractStructWithPointer [list p [makeptr 0]]
    } -result {Invalid value. Pointer is NULL. Error converting field ::cffi::test::S.p to a native value.} -returnCodes error
    test function-struct-pointerfield-in-0 "input param, nullok, NULL" -body {
        catch {S destroy}
        cffi::Struct create S {p {pointer unsafe nullok}}
        testDll function extractStructWithPointer {pointer unsafe nullok} {ins {struct.S byref in}}
        extractStructWithPointer [list p [makeptr 0]]
    } -result [makeptr 0]
    test function-struct-pointerfield-out-0 "output param, non-NULL" -body {
        catch {S destroy}
        cffi::Struct create S {p {pointer unsafe}}
        testDll function makeStructWithPointer void {ins {struct.S out} ptr {pointer unsafe}}
        makeStructWithPointer sout [makeptr 1]
        set sout
    } -result [list p [makeptr 1]]
    test function-struct-pointerfield-out-1 "output param, NULL" -body {
        catch {S destroy}
        cffi::Struct create S {p {pointer unsafe}}
        testDll function makeStructWithPointer void {ins {struct.S out} ptr {pointer nullok unsafe}}
        makeStructWithPointer sout [makeptr 0]
    } -result {}
    test function-struct-pointerfield-out-2 "output param, non-NULL" -body {
        catch {S destroy}
        cffi::Struct create S {p {pointer nullok unsafe}}
        testDll function makeStructWithPointer void {ins {struct.S out} ptr {pointer nullok unsafe}}
        makeStructWithPointer sout [makeptr 0]
        set sout
    } -result [list p [makeptr 0]]

    # Tests for default field values in structs passed
    test function-struct-field-defaults-0 "field defaults in passed struct" -body {
        cffi::Struct create SimpleWithDefaults {c {uchar {default 0}} ll {longlong {default 1}} s {short {default 2}}}
        cffi::Struct create OuterWithDefaults {f {float {default 3}} s {struct.SimpleWithDefaults {default {}}} p {pointer unsafe nullok {default NULL}}}
        testDll function {incrSimpleOuterStructByVal fn} struct.OuterWithDefaults {s {struct.OuterWithDefaults {default {}}}}
        fn
    } -constraints {
        libffi
    } -cleanup {
        rename fn {}
        SimpleWithDefaults destroy
        OuterWithDefaults destroy
    } -result [list f 4.0 s {c 1 ll 2 s 3} p [makeptr 1]]

    test function-struct-field-defaults-1 "field defaults in passed struct by ref" -body {
        cffi::Struct create SimpleWithDefaults {c {uchar {default 0}} ll {longlong {default 1}} s {short {default 2}}}
        testDll function {structCheck fn} int {s {struct.SimpleWithDefaults byref {default {}}} c schar ll longlong s short}
        fn {ll 42} 0 42 2
    } -cleanup {
        rename fn {}
        SimpleWithDefaults destroy
    } -result 1

    test function-struct-field-defaults-2 "field defaults of type string/unistring" -body {
        cffi::Struct create X [list \
                                   s [list string [list default $testStrings(ascii)]] \
                                   utf8 [list string.utf-8 [list default $testStrings(unicode)]] \
                                   jis [list string.jis0208 [list default $testStrings(jis0208)]] \
                                   uni [list unistring [list default $testStrings(unicode)]] \
                                  ]
        testDll function checkStructWithStrings int {s {struct.X byref}}
        checkStructWithStrings {}
    } -result 0

    test function-struct-field-defaults-error-0 "field invalid defaults" -body {
        cffi::Struct create SimpleWithDefaults {c {uchar {default -1}} ll {longlong {default 1}} s {short {default 2}}}
        testDll function {structCheck fn} int {s {struct.SimpleWithDefaults byref {default {}}} c schar ll longlong s short}
        fn {ll 42} 0 42 2
    } -cleanup {
        rename fn {}
        SimpleWithDefaults destroy
    } -result {Value -1 not in range. Must be within [0,255]. Error converting field ::cffi::test::SimpleWithDefaults.c to a native value.} -returnCodes error

    ###
    # structsize
    test function-struct-field-structsize-0 "field structsize" -body {
        cffi::Struct create SimpleWithStructSize {c {uchar structsize} ll {longlong structsize} s {short {default 43}}}
        testDll function {structCheck fn} int {s {struct.SimpleWithStructSize byref} c schar ll longlong s short}
        set size [dict get [SimpleWithStructSize info] Size]
        fn {ll 42} $size 42 43
    } -cleanup {
        rename fn {}
        SimpleWithStructSize destroy
    } -result 1

    ###
    # struct enum
    test function-struct-field-enum-0 "field enum" -setup {
        cffi::enum define E {A 1 B 2}
    } -body {
        cffi::Struct create SimpleWithStructSize {c uchar ll {longlong {enum E}} s {short {enum E} {default B}}}
        testDll function {structCheck fn} int {s {struct.SimpleWithStructSize byref} c schar ll longlong s short}
        set size [dict get [SimpleWithStructSize info] Size]
        fn {c 0 ll A} 0 1 2
    } -cleanup {
        cffi::enum delete E
        rename fn {}
        SimpleWithStructSize destroy
    } -result 1

    ###
    # struct bitmask
    test function-struct-field-bitmask-0 "field bitmask" -setup {
        cffi::enum define E {A 1 B 2}
    } -body {
        cffi::Struct create SimpleWithStructSize {c {uchar bitmask} ll {longlong bitmask {enum E}} s {short {enum E} {default 1}}}
        testDll function {structCheck fn} int {s {struct.SimpleWithStructSize byref} c schar ll longlong s short}
        set size [dict get [SimpleWithStructSize info] Size]
        fn {c {1 2} ll {A 4}} 3 5 1
    } -cleanup {
        cffi::enum delete E
        rename fn {}
        SimpleWithStructSize destroy
    } -result 1

    # Note -  tests for invalid parameter attrs are done in type.test

    ###
    # Return type tests

    #+ Numeric return types
    # Note Numeric returns types without attributes already tested earlier
    # Only test attributes here
    foreach type $intTypes {
        foreach {attr val} {
            zero 0
            nonzero 1
            nonzero -1
            nonnegative 0
            nonnegative 1
            positive 1
        } {
            set testnum -1
            set fn ${type}_to_$type
            testDll function $fn [list $type $attr] [list param $type]
            if {$val < 0 && $type in $unsignedIntTypes} continue
            test function-return-$type-$attr-$val-[incr testnum] "return $type $attr - $val" -setup {
            } -body {
                $fn $val
            } -result $val
        }
        test function-return-$type-byref-0 "return $type byref" -body {
            set fn ${type}_ret_byref
            testDll function $fn [list $type byref] [list param $type]
            $fn 42
        } -result 43
    }
    # Error cases
    foreach type $intTypes {
        foreach {attr val} {
            zero 1 
            zero -1 
            nonzero 0
            nonnegative -1
            positive 0
            positive -1
        } {
            set message "Function returned an error value."
            set testnum -1
            set fn ${type}_to_$type
            testDll function $fn [list $type $attr] [list param $type]
            if {$val < 0 && $type in $unsignedIntTypes} continue
            test function-return-$type-$attr-$val-error-[incr testnum] "return $type $attr - $val" -body {
                $fn $val
            } -result "Invalid value \"$val\". $message" -returnCodes error
        }
        test function-return-$type-byref-error-0 "return $type byref - requirement error" -body {
            set fn ${type}_ret_byref
            testDll function $fn [list $type byref zero] [list param $type]
            $fn 0
        } -result {Invalid value "1". Function returned an error value.} -returnCodes error
        test function-return-$type-byref-error-1 "return $type byref - return pointer null" -body {
            set fn pointer_to_pointer
            testDll function $fn [list $type byref] [list param {pointer unsafe nullok}]
            $fn 0^
        } -result {Invalid value. Function returned NULL pointer} -returnCodes error

        test function-return-$type-array-0 "return type $type\[\]" -body {
            testDll function ${type}_to_${type} $type\[2\] {}
        } -result "Invalid value \"$type\\\[2\\\]\". $errorMessages(arrayreturn)* Error defining function *" -match glob -returnCodes error
        foreach attr $paramDirectionAttrs {
            test function-return-$type-${attr}-0 "return type $type $attr" -body {
                testDll function ${type}_to_${type} [list $type $attr] {}
            } -result "Invalid value \"$type $attr\". $errorMessages(attrparsemode)* Error defining function *" -match glob -returnCodes error
        }
        foreach attr [linsert $pointerAttrs end nullifempty] {
            test function-return-$type-${attr}-0 "return type $type $attr" -body {
                testDll function ${type}_to_${type} [list $type $attr] {}
            } -result "Invalid value \"$type $attr\". $errorMessages(attrtype)* Error defining function *" -match glob -returnCodes error
        }
    }

    # errno, lasterror, winerror
    foreach {attr val} {
        zero 1
        zero -1
        nonzero 0
        nonnegative -1
        positive 0
        positive -1
    } {
        test function-return-errno-$attr-$val-0 "return errno" -setup {
            testDll function setErrno [list int $attr errno] {val int}
        } -body {
            list [catch {setErrno $val} result] [string equal $result $einvalMessage]
        } -result {1 1}

        test function-return-lasterror-$attr-$val-0 "return lasterror" -setup {
            testDll function setWinError [list int $attr lasterror] {val int}
        } -constraints win -body {
            list [catch {setWinError $val} result] $result
        } -result [list 1 "The data is invalid. "]

        if {$attr eq "zero"} {
            test function-return-winerror-zero-$val-0 "return zero winerror" -setup {
                testDll function {int_to_int getWinError} [list int zero winerror] {val int}
            } -constraints win -body {
                list [catch {getWinError 0xd} result] $result
            } -result [list 1 "The data is invalid. "]
        } else {
            test function-return-winerror-$attr-$val-0 "return $attr winerror" -constraints win -body {
                testDll function {int_to_int getWinError} [list int $attr winerror] {val int}
            } -result "Invalid value \"int $attr winerror\". $errorMessages(attrconflict)* Error defining function *" -match glob -returnCodes error
        }
    }

    # Attributes not permitted for real types
    foreach type $realTypes {
        test function-return-$type-array-0 "return type $type\[\]" -body {
            testDll function ${type}_to_${type} $type\[2\] {}
        } -result "Invalid value \"$type\\\[2\\\]\". $errorMessages(arrayreturn)* Error defining function *" -match glob -returnCodes error
        foreach attr [linsert $errorAttrs end nullifempty] {
            test function-return-$type-${attr}-0 "return type $type $attr" -body {
                testDll function ${type}_to_${type} [list $type $attr] {}
            } -result "Invalid value \"$type $attr\". $errorMessages(attrtype)* Error defining function *" -match glob -returnCodes error
        }
        foreach attr $paramDirectionAttrs {
            test function-return-$type-${attr}-0 "return type $type $attr" -body {
                testDll function ${type}_to_${type} [list $type $attr] {}
            } -result "Invalid value \"$type $attr\". $errorMessages(attrparsemode)* Error defining function *" -match glob -returnCodes error
        }
        foreach attr $pointerAttrs {
            test function-return-$type-${attr}-0 "return type $type $attr" -body {
                testDll function ${type}_to_${type} [list $type $attr] {}
            } -result "Invalid value \"$type $attr\". $errorMessages(attrtype)* Error defining function *" -match glob -returnCodes error
        }
    }

    # Return attributes for pointers
    foreach type {pointer pointer.::TAG pointer.TAG} tag {{} ::TAG ::cffi::test::TAG} {
        # safe
        test function-return-$type-safe-0 "return type $type safe" -body {
            testDll function pointer_to_pointer [list $type] {p {pointer unsafe}}
            set p [pointer_to_pointer 1^]
            list [cffi::pointer isvalid $p] [cffi::pointer dispose $p] [cffi::pointer isvalid $p]
        } -result {1 {} 0}
        test function-return-$type-safe-dup-0 "return type $type safe - dup" -cleanup {
            cffi::pointer dispose $p
        } -body {
            testDll function pointer_to_pointer [list $type] {p {pointer unsafe}}
            set p [pointer_to_pointer 1^]
            list [cffi::pointer isvalid $p] [catch {pointer_to_pointer 1^} result] [cffi::pointer isvalid $p]
        } -result {1 0 1}
        test function-return-$type-byref-safe-0 "return type $type byref safe" -body {
            testDll function pointer_ret_byref [list $type byref] {p {pointer unsafe}}
            set p [pointer_ret_byref 1^]
            list $p [cffi::pointer isvalid $p] [cffi::pointer dispose $p] [cffi::pointer isvalid $p]
        } -result [list [makeptr 1 $tag] 1 {} 0]

        # counted
        test function-return-$type-counted-0 "return type $type counted" -cleanup {
        } -body {
            testDll function pointer_to_pointer [list $type counted] {p {pointer unsafe}}
            set p [pointer_to_pointer 1^]
            set p2 [pointer_to_pointer 1^]
            list [string equal $p $p2] \
                [cffi::pointer isvalid $p] \
                [cffi::pointer dispose $p] \
                [cffi::pointer dispose $p2] \
                [cffi::pointer isvalid $p]
        } -result {1 1 {} {} 0}

        # unsafe
        test function-return-$type-unsafe-0 "return type $type unsafe" -body {
            testDll function pointer_to_pointer [list $type unsafe] {p {pointer unsafe}}
            set p [pointer_to_pointer 1^]
            list [cffi::pointer isvalid $p]
        } -result 0
        test function-return-$type-byref-unsafe-0 "return type $type byref unsafe" -body {
            testDll function pointer_ret_byref [list $type byref unsafe] {p {pointer unsafe}}
            set p [pointer_ret_byref 1^]
            list $p [cffi::pointer isvalid $p]
        } -result [list [makeptr 1 $tag] 0]

        # Pointer error attributes
        # safe
        test function-return-$type-nullok-0 "return type $type nullok" -body {
            testDll function pointer_to_pointer [list $type nullok] {p {pointer unsafe}}
            set p [pointer_to_pointer 1^]
            list [cffi::pointer isvalid $p] [cffi::pointer dispose $p] [cffi::pointer isvalid $p]
        } -result {1 {} 0}
        test function-return-$type-null-0 "return type $type - null" -body {
            testDll function pointer_to_pointer [list $type] {p {pointer nullok unsafe}}
            pointer_to_pointer 0^
        } -result "Invalid value *. Function returned an error value." -returnCodes error -match glob
        test function-return-$type-nullok-null-0 "return type $type - NULL" -body {
            testDll function pointer_to_pointer [list $type nullok] {p {pointer nullok unsafe}}
            set p [pointer_to_pointer 0^]
            list [cffi::pointer isvalid $p] $p
        } -result [list 0 [makeptr 0 $tag]]

        test function-return-$type-byref-nullok-null-0 "return type $type byref - nullok" -body {
            testDll function pointer_ret_byref [list $type byref nullok] {p {pointer unsafe nullok}}
            pointer_ret_byref 0^
        } -result [makeptr 0 $tag]
        test function-return-$type-byref-nullok-null-1 "return type $type byref - null" -body {
            testDll function pointer_ret_byref [list $type byref] {p {pointer unsafe nullok}}
            pointer_ret_byref 0^
        } -result "Invalid value *. Function returned an error value." -returnCodes error -match glob
        test function-return-$type-byref-nullok-null-2 "return type $type byref - null" -body {
            # The returned pointer to be dereferenced is itself NULL
            testDll function pointer_to_pointer [list $type byref] {p {pointer unsafe nullok}}
            pointer_to_pointer 0^
        } -result "Invalid value. Function returned NULL pointer" -returnCodes error -match glob

        test function-return-$type-errno-0 "return type $type - errno" -body {
            testDll function pointer_errno [list $type errno] {p {pointer unsafe nullok}}
            list [catch {pointer_errno 0^} result] [string equal $result $einvalMessage]
        } -result {1 1}
        test function-return-$type-lasterror-0 "return type $type - lasterror" -constraints win -body {
            testDll function pointer_lasterror [list $type lasterror] {p {pointer unsafe nullok}}
            list [catch {pointer_lasterror 0^} result] $result
        } -result {1 {The data is invalid. }}

        # counted
        test function-return-$type-counted-nullok-0 "return type $type counted" -body {
            testDll function pointer_to_pointer [list $type nullok counted] {p {pointer nullok unsafe}}
            set p [pointer_to_pointer 1^]
            list [cffi::pointer isvalid $p] [cffi::pointer dispose $p] [cffi::pointer isvalid $p]
        } -result {1 {} 0}
        test function-return-$type-counted-null-0 "return type $type counted nullok - null" -body {
            testDll function pointer_to_pointer [list $type counted] {p {pointer nullok unsafe}}
            pointer_to_pointer 0^
        } -result "Invalid value *. Function returned an error value." -returnCodes error -match glob
        test function-return-$type-counted-nullok-null-0 "return type $type counted nullok - NULL" -body {
            testDll function pointer_to_pointer [list $type nullok counted] {p {pointer nullok unsafe}}
            pointer_to_pointer 0^
        } -result [makeptr 0 $tag]
        test function-return-$type-counted-errno-0 "return type $type counted - errno" -body {
            testDll function pointer_errno [list $type counted errno] {p {pointer nullok unsafe}}
            list [catch {pointer_errno 0^} result] [string equal $result $einvalMessage]
        } -result {1 1}
        test function-return-$type-counted-lasterror-0 "return type $type counted - lasterror" -constraints win -body {
            testDll function pointer_lasterror [list $type counted lasterror] {p {pointer nullok unsafe}}
            list [catch {pointer_lasterror 0^} result] $result
        } -result {1 {The data is invalid. }}

        # unsafe
        test function-return-$type-unsafe-nullok-0 "return type $type unsafe nullok" -body {
            testDll function pointer_to_pointer [list $type unsafe nullok] {p {pointer nullok unsafe}}
            set p [pointer_to_pointer 1^]
            cffi::pointer isvalid $p
        } -result 0
        test function-return-$type-unsafe-null-0 "return type $type unsafe - null" -body {
            testDll function pointer_to_pointer [list $type unsafe] {p {pointer nullok unsafe}}
            pointer_to_pointer 0^
        } -result "Invalid value *. Function returned an error value." -returnCodes error -match glob
        test function-return-$type-unsafe-nullok-null-0 "return type $type unsafe nullok - NULL" -body {
            testDll function pointer_to_pointer [list $type unsafe nullok] {p {pointer nullok unsafe}}
            pointer_to_pointer 0^
        } -result [makeptr 0 $tag]
        test function-return-$type-unsafe-errno-0 "return type $type unsafe - errno" -body {
            testDll function pointer_errno [list $type unsafe errno] {p {pointer nullok unsafe}}
            list [catch {pointer_errno 0^} result] [string equal $result $einvalMessage]
        } -result {1 1}
        test function-return-$type-unsafe-lasterror-0 "return type $type unsafe - lasterror" -constraints win -body {
            testDll function pointer_lasterror [list $type unsafe lasterror] {p {pointer nullok unsafe}}
            list [catch {pointer_lasterror 0^} result] $result
        } -result {1 {The data is invalid. }}

        # checks that are not valid for pointers
        foreach attr {nonzero zero nonnegative positive nullifempty} {
            test function-return-$type-${attr}-0 "return type $type $attr" -body {
                testDll function pointer_to_pointer [list $type $attr] {p {pointer unsafe}}
            } -result "Invalid value \"$type $attr\". $errorMessages(attrtype)* Error defining function *" -match glob -returnCodes error
        }
        set attr winerror
        test function-return-$type-${attr}-0 "return type $type $attr" -constraints win -body {
            testDll function pointer_to_pointer [list $type $attr] {p {pointer unsafe}}
        } -result "Invalid value \"$type $attr\". $errorMessages(attrtype)* Error defining function *" -match glob -returnCodes error

        # Error cases
        test function-return-$type-array-0 "return type $type\[\]" -body {
            testDll function pointer_to_pointer $type\[2\] {}
        } -result "Invalid value \"$type\\\[2\\\]\". $errorMessages(arrayreturn)* Error defining function *" -match glob -returnCodes error
        test function-return-$type-dispose-0 "return type $type dispose" -body {
            testDll function pointer_to_pointer [list $type dispose] {p {pointer unsafe}}
        } -result "Invalid value \"$type dispose\". $errorMessages(attrparsemode)* Error defining function *" -match glob -returnCodes error
        foreach attr $paramDirectionAttrs {
            test function-return-$type-${attr}-0 "return type $type $attr" -body {
                testDll function pointer_to_pointer [list $type unsafe $attr] {p {pointer unsafe}}
            } -result "Invalid value \"$type unsafe $attr\". $errorMessages(attrparsemode)* Error defining function *" -match glob -returnCodes error
        }
    }

    # string, unistring return types
    foreach type {string string.utf-8 unistring} {
        test function-return-$type-NULL-nullok-0 "return $type NULL - nullok" -setup {
            testDll function pointer_to_pointer "$type nullok" {p {pointer nullok unsafe}}
        } -body {
            pointer_to_pointer 0^
        } -result ""
        test function-return-$type-NULL-0 "return $type NULL - error" -setup {
            testDll function pointer_to_pointer "$type" {p {pointer nullok unsafe}}
        } -body {
            pointer_to_pointer 0^
        } -result {Invalid value "". Function returned an error value.} -returnCodes error
    }
    test function-return-string-1 "return string ascii" -setup {
        testDll function ascii_return string {}
    } -body {
        ascii_return
    } -result "abc"
    test function-return-string-byref-1 "return string ascii byref" -setup {
        testDll function ascii_return_byref {string byref} {}
    } -body {
        ascii_return_byref
    } -result "abc"
    test function-return-string-2 "return string utf-8" -setup {
        testDll function utf8_return string.utf-8 {}
    } -body {
        utf8_return
    } -result "\xe0\xe1\xe2"
    test function-return-string-byref-2 "return string utf-8 byref" -setup {
        testDll function utf8_return_byref {string.utf-8 byref} {}
    } -body {
        utf8_return_byref
    } -result "\xe0\xe1\xe2"
    test function-return-string-3 "return string jis0208" -setup {
        testDll function jis0208_return string.jis0208 {}
    } -body {
        jis0208_return
    } -result "\u543e"
    test function-return-string-byref-3 "return string jis0208 byref" -setup {
        testDll function jis0208_return_byref {string.jis0208 byref} {}
    } -body {
        jis0208_return_byref
    } -result "\u543e"
    test function-return-string-4 "return string ascii - nullok" -setup {
        testDll function ascii_return {string nullok} {}
    } -body {
        ascii_return
    } -result "abc"
    test function-return-unistring-1 "return unistring" -setup {
        testDll function unistring_return unistring {}
    } -body {
        unistring_return
    } -result "\xe0\xe1\xe2"
    test function-return-unistring-byref-1 "return unistring byref" -setup {
        testDll function unistring_return_byref {unistring byref} {}
    } -body {
        unistring_return_byref
    } -result "\xe0\xe1\xe2"
    test function-return-unistring-2 "return unistring - nullok" -setup {
        testDll function unistring_return {unistring nullok} {}
    } -body {
        unistring_return
    } -result "\xe0\xe1\xe2"

    test function-return-string-byref-null-0 "return string byref - NULL" -setup {
        testDll function pointer_to_pointer {string byref nullok} {p {pointer nullok unsafe}}
    } -body {
        # THe "nullok" annotation should refer to dereferenced pointer, not
        # returned pointer value
        pointer_to_pointer 0^
    } -result {Invalid value. Function returned NULL pointer} -returnCodes error
    test function-return-string-byref-null-1 "return string byref - NULL" -setup {
        testDll function pointer_ret_byref {string byref nullok} {p {pointer nullok unsafe}}
    } -body {
        # THe "nullok" annotation should refer to dereferenced pointer, not
        # returned pointer value
        pointer_ret_byref 0^
    } -result ""

    # Return type - struct
    test function-struct-return-0 "Return struct with input struct" -constraints {
        libffi
    } -setup {
        cffi::Struct create ::Inner {c schar ll longlong s short}
        cffi::Struct create ::Outer {f float s struct.::Inner p {pointer unsafe}}
        testDll function {incrSimpleOuterStructByVal f} struct.::Outer {s struct.::Outer}
    } -body {
        set inner {c 0 ll 1 s 2}
        set outer [list f 10 s $inner p [makeptr 1]]
        f $outer
    } -result [list f 11.0 s [list c 1 ll 2 s 3] p [makeptr 2]]


    test function-struct-return-1 "return struct with strings" -constraints libffi -body {
        testDll function returnStructWithStrings struct.StructWithStrings {}
        returnStructWithStrings
    } -result [makeStructWithStrings]

    test function-struct-return-error-0 "Return type struct - error " -constraints {
        dyncall
    } -body {
        # Prototype does not match C. Does not have to!
        testDll function getTestStruct struct.TestStruct {}
    } -result "Invalid value \"struct.TestStruct\". $errorMessages(typeparsemode)" -returnCodes error


    test function-struct-return-error-1 "Return struct containing array" -constraints {
        libffi
    } -setup {
        testDll function returnTestStruct struct.TestStruct {}
    } -body {
        set s [returnTestStruct]
    } -result {The libffi backend does not support arrays by value. Define as struct with corresponding number of fields as a workaround.} -returnCodes error


    test function-struct-return-byref-0 "return struct byref" -body {
        testDll function returnTestStructByRef {struct.::TestStruct byref} {}
        checkTestStruct [returnTestStructByRef]
    } -result [list ]

    test function-struct-return-byref-1 "return struct byref - null pointer nullok" -setup {
        cffi::Struct create Inner {c schar ll longlong s short}
        cffi::Struct create Outer {i {int {default 1}} s {struct.Inner {default {c 10 ll 100 s 1000}}} str {string {default foo}}}
        testDll function pointer_to_pointer {struct.Outer byref nullok} {p {pointer unsafe nullok}}
    } -body {
        pointer_to_pointer NULL
    } -result [list i 1 s {c 10 ll 100 s 1000} str foo]

    test function-struct-return-byref-2 "return struct byref - null pointer nullok inner struct clear" -setup {
        cffi::Struct create Inner {c schar ll longlong s short} -clear
        cffi::Struct create Outer {i {int {default 1}} s {struct.Inner {default {}}} str {string {default foo}}}
        testDll function pointer_to_pointer {struct.Outer byref nullok} {p {pointer unsafe nullok}}
    } -body {
        pointer_to_pointer NULL
    } -result [list i 1 s {c 0 ll 0 s 0} str foo]

    test function-struct-return-byref-error-0 "return struct byref - null pointer nullok, missing default" -setup {
        cffi::Struct create Inner {c schar ll longlong s short}
        cffi::Struct create Outer {i {int {default 1}} s {struct.Inner {default {c 10 ll 100 s 1000}}} str {string}}
        testDll function pointer_to_pointer {struct.Outer byref nullok} {p {pointer unsafe nullok}}
    } -body {
        pointer_to_pointer NULL
    } -result {Cannot construct a default value for struct ::cffi::test::Outer.} -returnCodes error

    test function-struct-return-byref-error-1 "return struct byref - null pointer" -setup {
        cffi::Struct create Inner {c schar ll longlong s short} -clear
        cffi::Struct create Outer {i {int {default 1}} s struct.Inner str {string}}
        testDll function pointer_to_pointer {struct.Outer byref} {p {pointer unsafe nullok}}
    } -body {
        pointer_to_pointer NULL
    } -result {Invalid value. Function returned NULL pointer.} -returnCodes error

    ## Error attributes for strings

    # Other return types are not permitted - test here. Note it does not
    # matter that actual C prototype does not match what we define here since
    # the definition should barf anyways.
    foreach type {binary} {
        test function-return-$type-0 "Return type $type" -body {
            # Prototype does not match C. Does not have to!
            testDll function ${type}_out $type {}
        } -result "Invalid value \"$type\". $errorMessages(typeparsemode)* Error defining function *" -match glob -returnCodes error
    }
    foreach type {chars unichars bytes} {
        test function-return-$type-0 "Return type $type" -body {
            # Prototype does not match C. Does not have to!
            testDll function string_out ${type}\[2\] {}
        } -result "Invalid value \"$type\\\[2\\\]\". $errorMessages(typeparsemode)* Error defining function *" -match glob -returnCodes error
    }

    ###
    # Tests for defaults
    foreach type $intTypes {
        if {$type in $realTypes} {
            set result [list 0.0 1.0]
        } else {
            set result [list 0 1]
        }
        test function-paramdefault-$type-0 "function $type default" -body {
            set fn ${type}_to_$type
            testDll function $fn $type [list x [list $type [list default 1]]]
            list [$fn 0] [$fn]
        } -result [list 0 1]
        test function-paramdefault-error-$type-0 "function $type default - wrong type" -body {
            testDll function ${type}_to_$type $type [list x [list $type [list default 1]]]
            ${type}_to_$type notanumber
        } -result {expected integer but got "notanumber"} -returnCodes error -match glob
    }
    foreach type {string string.utf-8 chars[10] chars.utf-8[10]} {
        test function-paramdefault-$type-0 "function $type default" -body {
            set fn string_to_int
            testDll function $fn int [list x [list $type [list default 42]]]
            list [$fn 24] [$fn]
        } -result [list 24 42]
    }
    foreach type {binary bytes[10]} {
        test function-paramdefault-$type-0 "function $type default" -body {
            set fn string_to_int
            testDll function $fn int [list x [list $type [list default "42\0"]]]
            list [$fn "24\0"] [$fn]
        } -result [list 24 42]
    }

    test function-paramdefault-pointer-0 "Function pointer default" -body {
        set fn pointer_to_pointer
        testDll function $fn {pointer unsafe} [list p [list pointer unsafe [list default [makeptr 42 TAG]]]]
        list [$fn [makeptr 24]] [$fn]
    } -result [list [makeptr 24] [makeptr 42]]
    test function-paramdefault-struct-0 "Function struct default" -setup {
        cffi::Struct create ::S {c uchar ll longlong s short}
    } -body {
        set ll 0x7fffffffffff
        set def [list c 1 ll $ll s 2]
        testDll function structCheck int [list s [list struct.::S byref [list default $def]] c {uchar {default 1}} ll [list longlong [list default $ll]] s {short {default 2}}]
        structCheck
    } -result 1

    test function-paramdefault-0 "function multiargs defaults" -body {
        testDll function threeargs int {a {int {default 0}} b {int {default 10}} c {int {default 100}}}
        list [threeargs] [threeargs 1] [threeargs 1 2] [threeargs 1 2 3]
    } -result {110 111 103 6}
    test function-paramdefault-1 "function multiargs - missing default" -body {
        testDll function threeargs int {a {int {default 0}} b int c {int {default 100}}}
        threeargs 1 2 3
    } -result 6
    test function-paramdefault-error-0 "function multiargs - missing default error" -body {
        testDll function threeargs int {a {int {default 0}} b int c {int {default 100}}}
        threeargs 1
    } -result {Syntax: threeargs a b c} -returnCodes error
    test function-paramdefault-error-1 "function multiargs - missing default error last arg" -body {
        testDll function threeargs int {a {int {default 0}} b {int {default 100}} c int}
        threeargs 1 2
    } -result {Syntax: threeargs a b c} -returnCodes error

    ###
    # Array tests - empty arrays, all types
    foreach type [list {*}$numericTypes pointer struct.::TestStruct chars unichars bytes] {
        # Test for zero size array with nullok
        test function-array-count0-$type-in-0 {array input size 0} -setup {
            # Fake out function as taking array. Doesn't matter as we pass NULL
            set fn pointer_add
            testDll function $fn {pointer nullok unsafe} [list inarray [list $type\[n\] nullok] n int]
        } -body {
            $fn {} 0
        } -result [makeptr 0]
        test function-array-count0-$type-in-1 {array input size 0, array not empty} -setup {
            # Fake out function as taking array. Doesn't matter as we pass NULL
            set fn pointer_add
            testDll function $fn {pointer nullok unsafe} [list inarray [list $type\[n\] nullok] n int]
        } -body {
            $fn {1} 0
        } -result [makeptr 0]

        test function-array-count0-$type-out-0 {array output size 0} -setup {
            # Fake out function as taking array. Doesn't matter as we pass NULL
            set fn pointer_add
            testDll function $fn {pointer nullok unsafe} [list inarray [list $type\[n\] nullok out] n int]
            unset -nocomplain outvar
        } -body {
            list [$fn {} 0] [info exists outvar]
        } -result [list [makeptr 0] 0]

        test function-array-count0-$type-inout-0 {array inout size 0} -setup {
            # Fake out function as taking array. Doesn't matter as we pass NULL
            set fn pointer_add
            testDll function $fn {pointer nullok unsafe} [list inarray [list $type\[n\] nullok out] n int]
            unset -nocomplain outvar
        } -body {
            list [$fn {} 0] [info exists outvar]
        } -result [list [makeptr 0] 0]
    }

    ###
    # Array tests - numerics
    foreach type $numericTypes {
        # This matches output of *_array_out function
        foreach count {1 10 10000} {
            set vals [seq 0 $count]; # This matches what *_array_out functions return
            if {$type in $realTypes} {
                set vals [lmap val $vals {expr {double($val)}}]
            }
            set nvals [llength $vals]
            # Skip types that will overflow
            if {$count <= 10 || $type ni {schar uchar short ushort float}} {
                test function-array-$type-$count-in-0 "Array input parameters" -body {
                    testDll function ${type}_array_in $type [list n int arr $type\[$nvals\]]
                    ${type}_array_in $nvals $vals
                } -result [::tcl::mathop::+ {*}$vals]
                test function-array-$type-$count-in-1 "Array input parameters - count greater than arg length" -body {
                    set buflen [expr {$nvals + 1000}]
                    testDll function ${type}_array_in $type [list n int arr $type\[$buflen\]]
                    # Extra slots should have been zeroed
                    ${type}_array_in $buflen $vals
                } -result [::tcl::mathop::+ {*}$vals]
                test function-array-$type-$count-in-4 "Array input parameters - dynamic array" -body {
                    testDll function ${type}_array_in $type [list n int arr $type\[n\]]
                    ${type}_array_in $nvals $vals
                } -result [::tcl::mathop::+ {*}$vals]
                test function-array-$type-$count-in-5 "Array input parameters - dynamic array, array first" -body {
                    testDll function ${type}_array_count_in $type [list arr $type\[n\] n int]
                    ${type}_array_in $nvals $vals
                } -result [::tcl::mathop::+ {*}$vals]

                test function-array-$type-$count-out-0 "Array out parameters" -body {
                    testDll function ${type}_array_out void [list n int arr [list $type\[$nvals\] out]]
                    ${type}_array_out $nvals out
                    set out
                } -result $vals
                test function-array-$type-$count-out-1 "Array out parameters - dynamic array" -body {
                    testDll function ${type}_array_out void [list n int arr [list $type\[n\] out]]
                    ${type}_array_out $nvals out
                    set out
                } -result $vals

                test function-array-$type-$count-inout-0 "Array inout parameters" -body {
                    testDll function ${type}_array_inout void [list n int arr [list $type\[$nvals\] inout]]
                    set out $vals
                    ${type}_array_inout $nvals out
                    set out
                } -result [lmap v $vals {expr {$v+1}}]
                test function-array-$type-$count-inout-1 "Array inout parameters - dynamic array" -body {
                    testDll function ${type}_array_inout void [list n int arr [list $type\[n\] inout]]
                    set out $vals
                    ${type}_array_inout $nvals out
                    set out
                } -result [lmap v $vals {expr {$v+1}}]
            }
        }

        # Tests added for bug#41. Some may duplicate existing tests above
        test function-array-count-$type-0 {Copy array when count param is AFTER array pointer} -setup {
            set fn ${type}_array_count_copy
            testDll function $fn void [list inparam $type\[n_in\] n_in int outparam [list $type\[n_out\] out] n_out int]
        } -body {
            $fn {1 2 3} 3 outarr 2
            set outarr
        } -result [expr {$type in $realTypes ? {1.0 2.0} : {1 2}}]
        test function-count-array-$type-0 {Copy array when count param is BEFORE array pointer} -setup {
            set fn ${type}_count_array_copy
            testDll function $fn void [list n_out int outparam [list $type\[n_out\] out] n_in int inparam $type\[n_in\]]
        } -body {
            $fn 3 outarr 2 {1 2}
            set outarr
        } -result [expr {$type in $realTypes ? {1.0 2.0 0.0} : {1 2 0}}]
        test function-array-count-$type-error-0 {Copy array when count param is AFTER array pointer} -setup {
            set fn ${type}_array_count_copy
            testDll function $fn void [list inparam $type\[n_in\] n_in int outparam [list $type\[n_out\] out] n_out int]
        } -body {
            $fn {1 2 3} 3 outarr -1
            set outarr
        } -result {Value -1 not in range. Must be within [1,2147483647].} -returnCodes error
        test function-array-count-$type-error-1 {Copy array when count param is AFTER array pointer} -setup {
            set fn ${type}_array_count_copy
            testDll function $fn void [list inparam $type\[n_in\] n_in int outparam [list $type\[n_out\] out] n_out int]
        } -body {
            $fn {1 2 3} 0 outarr 2
            set outarr
        } -result {Passing a zero size array requires the nullok annotation.} -returnCodes error
        test function-count-array-$type-error-0 {Copy array when count param is BEFORE array pointer} -setup {
            set fn ${type}_count_array_copy
            testDll function $fn void [list n_out int outparam [list $type\[n_out\] out] n_in int inparam $type\[n_in\]]
        } -body {
            $fn -1 outarr 2 {1 2}
            set outarr
        } -result {Value -1 not in range. Must be within [1,2147483647].} -returnCodes error
        test function-count-array-$type-error-1 {Copy array when count param is BEFORE array pointer} -setup {
            set fn ${type}_count_array_copy
            testDll function $fn void [list n_out int outparam [list $type\[n_out\] out] n_in int inparam $type\[n_in\]]
        } -body {
            $fn 2 outarr 0 {1 2}
            set outarr
        } -result {Passing a zero size array requires the nullok annotation.} -returnCodes error

        # errors
        foreach direc {in out inout} {
            test function-array-$type-$direc-error-0 "Array negative index - $direc" -body {
                testDll function ${type}_array_$direc $type [list n int arr $type\[0\]]
            } -result "Invalid value \"$type\\\[0\\\]\". Invalid array size or extra trailing characters. Error defining function *" -match glob -returnCodes error
            test function-array-$type-$direc-error-1 "Array negative index - $direc" -body {
                testDll function ${type}_array_$direc $type [list n int arr $type\[-1\]]
            } -result "Invalid value \"$type\\\[-1\\\]\". Invalid array size or extra trailing characters. *" -match glob -returnCodes error
        }
        test function-array-$type-in-error-2 "Array undefined name index" -body {
            testDll function ${type}_array_in $type [list n int arr $type\[m\]]
        } -result {Parameter "m" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -returnCodes error
        test function-array-$type-in-error-3 "Array index name wrong type" -body {
            testDll function ${type}_array_in $type [list n string arr $type\[n\]]
        } -result {Parameter "*" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -match glob -returnCodes error
        test function-array-$type-in-error-4 "Array index name is array" -body {
            testDll function ${type}_array_in $type [list n int\[3\] arr $type\[n\]]
        } -result {Parameter "n" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -returnCodes error
        test function-array-$type-in-error-5 "Array index is out param" -body {
            testDll function ${type}_array_in $type [list {n out} int arr $type\[n\]]
        } -result {Parameter "n" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -returnCodes error
        test function-array-$type-in-error-6 "Array index is inout param" -body {
            testDll function ${type}_array_in $type [list {n inout} int arr $type\[n\]]
        } -result {Parameter "n" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -returnCodes error
        test function-array-$type-in-error-6 "Array index is byref param" -body {
            testDll function ${type}_array_in $type [list {n byref} int arr $type\[n\]]
        } -result {Parameter "n" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -returnCodes error

        # "in" not in this loop because function prototype is different
        foreach direc {out inout} {
            test function-array-$type-$direc-error-2 "Array undefined name index - $direc" -body {
                testDll function ${type}_array_$direc $type [list n int arr [list $type\[m\] $direc]]
            } -result {Parameter "m" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -returnCodes error
            test function-array-$type-$direc-error-3 "Array index name wrong type - $direc" -body {
                testDll function ${type}_array_$direc $type [list n string arr [list $type\[n\] $direc]]
            } -result {Parameter "*" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -match glob -returnCodes error
            test function-array-$type-$direc-error-4 "Array index name is array - $direc" -body {
                testDll function ${type}_array_$direc $type [list n int\[3\] arr [list $type\[n\] $direc]]
            } -result {Parameter "n" not found or inaccessible. Could not find referenced count for dynamic array, possibly wrong type or not scalar. Error defining function *} -match glob -returnCodes error
        }
        test function-array-$type-in-typeerror-0 "Array element wrong type - in" -body {
            testDll function ${type}_array_in $type [list n int arr $type\[$nvals\]]
            ${type}_array_in $nvals {0 a 1}
        } -result {expected * but got "a"} -returnCodes error -match glob
        test function-array-$type-inout-typerrror-0 "Array element wrong type - inout" -body {
            testDll function ${type}_array_inout $type [list n int arr [list $type\[$nvals\] inout]]
            ${type}_array_in $nvals {0 a 1}
        } -result {expected * but got "a"} -returnCodes error -match glob
    }

    # Array tests - pointers
    test function-array-pointer-0 "input safe output unsafe" -setup {
        purge_pointers
        set safe_ptrs [make_safe_pointers 1 2 3]
        unset -nocomplain unsafe_ptrs
    } -cleanup {
        purge_pointers
        unset -nocomplain safe_ptrs unsafe_ptrs
    } -body {
        testDll function pointer_array_incr void {inp pointer[n] outp {pointer[n] out unsafe} n int}
        pointer_array_incr $safe_ptrs unsafe_ptrs [llength $safe_ptrs]
        set unsafe_ptrs
    } -result [make_unsafe_pointers 2 3 4]
    test function-array-pointer-1 "input unsafe output safe" -setup {
        purge_pointers
        set unsafe_ptrs [make_unsafe_pointers 1 2 3]
        unset -nocomplain safe_ptrs
    } -cleanup {
        purge_pointers
        unset -nocomplain safe_ptrs unsafe_ptrs
    } -body {
        testDll function pointer_array_incr void {inp {pointer[n] unsafe} outp {pointer[n] out} n int}
        pointer_array_incr $unsafe_ptrs safe_ptrs [llength $unsafe_ptrs]
        list $safe_ptrs [lsort [cffi::pointer list]]
    } -result [list [make_unsafe_pointers 2 3 4] [make_unsafe_pointers 2 3 4]]
    test function-array-pointer-2 "inout counted" -setup {
        purge_pointers
        set ptrs1 [make_counted_pointers 1 2 3]
        set ptrs2 [make_counted_pointers 4 5 6]
    } -cleanup {
        purge_pointers
        unset -nocomplain ptrs1 ptrs2
    } -body {
        testDll function pointer_array_exchange void {inout1 {pointer[n] inout counted} inout2 {pointer[n] inout counted} n int}
        pointer_array_exchange ptrs1 ptrs2 [llength $ptrs1]
        list $ptrs1 $ptrs2
    } -result [list [make_unsafe_pointers 4 5 6] [make_unsafe_pointers 1 2 3]]
    test function-array-pointer-3 "input dispose output unsafe" -setup {
        purge_pointers
        set safe_ptrs [make_safe_pointers 1 2 3]
        unset -nocomplain unsafe_ptrs
    } -cleanup {
        purge_pointers
        unset -nocomplain safe_ptrs unsafe_ptrs
    } -body {
        testDll function pointer_array_incr void {inp {pointer[n] dispose} outp {pointer[n] out unsafe} n int}
        pointer_array_incr $safe_ptrs unsafe_ptrs [llength $safe_ptrs]
        list $unsafe_ptrs [cffi::pointer list]
    } -result [list [make_unsafe_pointers 2 3 4] {}]
    test function-array-pointer-4 "input NULL output unsafe" -setup {
        purge_pointers
        set in_ptrs [make_unsafe_pointers 0 1 2]
        unset -nocomplain unsafe_ptrs
    } -cleanup {
        purge_pointers
        unset -nocomplain in_ptrs unsafe_ptrs
    } -body {
        testDll function pointer_array_incr void {inp pointer[n] outp {pointer[n] out unsafe} n int}
        pointer_array_incr $in_ptrs unsafe_ptrs [llength $in_ptrs]
        set unsafe_ptrs
    } -result {Invalid value. Pointer is NULL.} -returnCodes error
    test function-array-pointer-5 "input nullok NULL output unsafe" -setup {
        purge_pointers
        set in_ptrs [make_unsafe_pointers 0 1 2]
        unset -nocomplain unsafe_ptrs
    } -cleanup {
        purge_pointers
        unset -nocomplain in_ptrs unsafe_ptrs
    } -body {
        testDll function pointer_array_incr void {inp {pointer[n] unsafe nullok} outp {pointer[n] out unsafe} n int}
        pointer_array_incr $in_ptrs unsafe_ptrs [llength $in_ptrs]
        set unsafe_ptrs
    } -result [make_unsafe_pointers 1 2 3]
    test function-array-pointer-6 "inout nullok" -setup {
        purge_pointers
        set ptrs1 [make_unsafe_pointers 0 1 2]
        set ptrs2 [make_unsafe_pointers 4 5 6]
    } -cleanup {
        purge_pointers
        unset -nocomplain ptrs1 ptrs2
    } -body {
        # Note - inout2 does not have nullok - should still allow output NULL pointer
        testDll function pointer_array_exchange void {inout1 {pointer[n] inout unsafe nullok} inout2 {pointer[n] inout unsafe} n int}
        pointer_array_exchange ptrs1 ptrs2 [llength $ptrs1]
        list $ptrs1 $ptrs2
    } -result [list [make_unsafe_pointers 4 5 6] [make_unsafe_pointers 0 1 2]]
    test function-array-pointer-7 "Bug #95 - dispose array of size 1" -setup {
        purge_pointers
        set safe_ptrs [make_safe_pointers 1]
        unset -nocomplain unsafe_ptrs
    } -cleanup {
        purge_pointers
        unset -nocomplain safe_ptrs unsafe_ptrs
    } -body {
        testDll function pointer_array_incr void {inp {pointer[n] dispose} outp {pointer[n] out unsafe} n int}
        pointer_array_incr $safe_ptrs unsafe_ptrs [llength $safe_ptrs]
        list $unsafe_ptrs [cffi::pointer list]
    } -result [list [make_unsafe_pointers 2] {}]


    ###
    # Function failure tests with onerror:
 
    proc onerror_ignore {args} {
        upvar 1 handler_output v
        incr v
        return "onerror_ignore: $args"
    }
    proc onerror_raise {errormessage args} {
        upvar 1 handler_output v
        incr v
        error "$errormessage: $args"
    }
    proc testexcepts {label comment retannot paramannot expected args} {
        test $label $comment -setup {
            unset -nocomplain outvar
            set handler_output 99
        } -body {
            testDll function int_out [list int {*}$retannot] [list inparam int outparam [list int {*}$paramannot]]
            list \
                [catch {int_out 42 outvar} result] \
                $result \
                [expr {[info exists outvar] ? $outvar : 0}] \
                $handler_output
        } -result $expected
    }
    proc testptrexcepts {label comment ptr retannot paramannot expected args} {
        test $label $comment -setup {
            unset -nocomplain outvar
        } -body {
            testDll function pointer_reflect [list pointer unsafe {*}$retannot] [list inparam {pointer nullok unsafe} outparam [list pointer nullok unsafe {*}$paramannot]]
            list \
                [catch {pointer_reflect $ptr outvar} result] \
                $result \
                [expr {[info exists outvar] ? $outvar : 0}]
        } -result $expected
    }

    testexcepts function-fail-0 "Fail without onerror handler" \
        [list zero] \
        out \
        [list 1 {Invalid value "44". Function returned an error value.} 0 99]
    testptrexcepts function-fail-1 "Pointer fail without onerror handler" \
        [makeptr 0] \
        {} \
        out \
        [list 1 "Invalid value \"[makeptr 0]\". Function returned an error value." 0]
    testexcepts function-onerror-ignore-0 "Success with onerror onerror_ignore" \
        [list nonzero {onerror onerror_ignore}] \
        out \
        [list 0 44 43 99]
    testexcepts function-onerror-ignore-1 "Fail with onerror onerror_ignore" \
        [list zero {onerror onerror_ignore}] \
        out \
        [list 0 "onerror_ignore: {In {inparam 42} Out {} Result 44 Command ::cffi::test::int_out}" 0 100]
    testptrexcepts function-onerror-ignore-2 "Pointer fail with onerror handler" \
        [makeptr 0] \
        {{onerror {return -level 0}}} \
        out \
        [list 0 [list In [list inparam [makeptr 0]] Out [list ] Result [makeptr 0] Command ::cffi::test::pointer_reflect] 0]
    testexcepts function-onerror-raise-0 "Success with onerror onerror_raise" \
        [list nonzero {onerror onerror_raise}] \
        out \
        [list 0 44 43 99]
    testexcepts function-onerror-raise-1 "Fail with onerror onerror_raise" \
        [list zero {onerror {onerror_raise prefixarg}}] \
        out \
        [list 1 {prefixarg: {In {inparam 42} Out {} Result 44 Command ::cffi::test::int_out}} 0 100]
    testptrexcepts function-onerror-raise-2 "Pointer fail with onerror handler" \
        [makeptr 0] \
        {{onerror error}} \
        out \
        [list 1 [list In [list inparam [makeptr 0]] Out {} Result [makeptr 0] Command ::cffi::test::pointer_reflect] 0]

    ###
    # storealways

    testexcepts function-storealways-fail-0 "Storealways - Fail without onerror handler" \
        [list zero] \
        [list out storealways] \
        [list 1 {Invalid value "44". Function returned an error value.} 43 99]
    testptrexcepts function-storealways-fail-1 "Storealways - Pointer fail without onerror handler" \
        [makeptr 0] \
        {} \
        [list out storealways] \
        [list 1 "Invalid value \"[makeptr 0]\". Function returned an error value." [makeptr 0]]

    testexcepts function-storealways-onerror-ignore-0 "Storealways - Success with onerror onerror_ignore" \
        [list nonzero {onerror onerror_ignore}] \
        [list out storealways] \
        [list 0 44 43 99]
    testexcepts function-storealways-onerror-ignore-1 "Storealways - Fail with onerror onerror_ignore" \
        [list zero {onerror onerror_ignore}] \
        [list out storealways] \
        [list 0 "onerror_ignore: {In {inparam 42} Out {outparam 43} Result 44 Command ::cffi::test::int_out}" 43 100]
    testptrexcepts function-storealways-onerror-ignore-2 "Storealways - Pointer fail with onerror handler" \
        [makeptr 0] \
        {{onerror {list prefix}}} \
        [list out storealways] \
        [list 0 [list prefix [list In [list inparam [makeptr 0]] Out [list outparam [makeptr 0]] Result [makeptr 0] Command ::cffi::test::pointer_reflect]] [makeptr 0]]
    testexcepts function-storealways-onerror-raise-0 "Storealways - Success with onerror onerror_raise" \
        [list nonzero {onerror onerror_raise}] \
        [list out storealways] \
        [list 0 44 43 99]
    testexcepts function-storealways-onerror-raise-1 "Storealways - Fail with onerror onerror_raise" \
        [list zero {onerror {onerror_raise prefixarg}}] \
        [list out storealways] \
        [list 1 {prefixarg: {In {inparam 42} Out {outparam 43} Result 44 Command ::cffi::test::int_out}} 43 100]
    testptrexcepts function-storealways-onerror-raise-2 "Storealways - Pointer fail with onerror handler" \
        [makeptr 0] \
        {{onerror error}} \
        [list out storealways] \
        [list 1 [list In [list inparam [makeptr 0]] Out [list outparam [makeptr 0]] Result [makeptr 0] Command ::cffi::test::pointer_reflect] [makeptr 0]]


    ###
    # storeonerror

    testexcepts function-storeonerror-fail-0 "Storeonerror - Fail without onerror handler" \
        [list zero] \
        [list out storeonerror] \
        [list 1 {Invalid value "44". Function returned an error value.} 43 99]
    testptrexcepts function-storeonerror-fail-1 "Storeonerror - Pointer fail without onerror handler" \
        [makeptr 0] \
        {} \
        [list out storeonerror] \
        [list 1 "Invalid value \"[makeptr 0]\". Function returned an error value." [makeptr 0]]
    testexcepts function-storeonerror-onerror-ignore-0 "Storeonerror - Success with onerror onerror_ignore" \
        [list nonzero {onerror onerror_ignore}] \
        [list out storeonerror] \
        [list 0 44 0 99]
    testexcepts function-storeonerror-onerror-ignore-1 "Storeonerror - Fail with onerror onerror_ignore" \
        [list zero {onerror onerror_ignore}] \
        [list out storeonerror] \
        [list 0 {onerror_ignore: {In {inparam 42} Out {outparam 43} Result 44 Command ::cffi::test::int_out}} 43 100]
    testptrexcepts function-storeonerror-onerror-ignore-2 "Storeonerror - Pointer fail with onerror handler" \
        [makeptr 0] \
        {{onerror {list}}} \
        [list out storeonerror] \
        [list 0 [list [list In [list inparam [makeptr 0]] Out [list outparam [makeptr 0]] Result [makeptr 0] Command ::cffi::test::pointer_reflect]] [makeptr 0]]
    testexcepts function-storeonerror-onerror-raise-0 "Storeonerror - Success with onerror onerror_raise" \
        [list nonzero {onerror onerror_raise}] \
        [list out storeonerror] \
        [list 0 44 0 99]
    testexcepts function-storeonerror-onerror-raise-1 "Storeonerror - Fail with onerror onerror_raise" \
        [list zero {onerror {onerror_raise prefixarg}}] \
        [list out storeonerror] \
        {1 {prefixarg: {In {inparam 42} Out {outparam 43} Result 44 Command ::cffi::test::int_out}} 43 100}
    testptrexcepts function-storeonerror-onerror-raise-2 "Storeonerror - Pointer fail with onerror handler" \
        [makeptr 0] \
        {{onerror error}} \
        [list out storeonerror] \
        [list 1 [list In [list inparam [makeptr 0]] Out [list outparam [makeptr 0]] Result [makeptr 0] Command ::cffi::test::pointer_reflect] [makeptr 0]]

    ###
    # enum tests

    foreach type $intTypes {
        test function-enum-$type-0 "function enum $type" -setup {
            cffi::enum delete *
            cffi::enum define E {x 1 a 42}
        } -body {
            set fn ${type}_to_$type
            testDll function $fn $type [list inparam [list $type {enum E}]]
            list [$fn 24] [$fn a]
        } -result [list 24 42]
        test function-enum-$type-1 "function enum $type byref" -setup {
            cffi::enum delete *
            cffi::enum define E {x 1 a 42}
            set fn ${type}_byref
            testDll function $fn $type [list inparam [list $type {enum E} byref]]
        } -body {
            $fn a
        } -result 44
        test function-enum-$type-2 "function enum $type inout" -setup {
            cffi::enum delete *
            cffi::enum define E {x 1 y 2 z 3 a 42}
            set fn ${type}_inout
            testDll function $fn $type [list inoutparam [list $type {enum E} inout]]
        } -body {
            set param x
            set param2 99
            list [$fn param] $param [$fn param2] $param2
        } -result {3 y 101 100}
        test function-enum-$type-3 "function enum $type return and out" -setup {
            cffi::enum delete *
            cffi::enum define E {x 1 y 2 z 3}
            set fn ${type}_out
            testDll function $fn [list $type {enum E}] [list inparam [list $type {enum E}] outparam [list $type {enum E} out]]
        } -body {
            list [$fn x param] $param [$fn 99 param2] $param2
        } -result {z y 101 100}
        test function-enum-$type-4 "function enum $type array in" -setup {
            cffi::enum delete *
            cffi::enum define E {a 1 b 2}
        } -body {
            testDll function ${type}_array_in $type [list n int arr "$type\[3\] {enum E}"]
            ${type}_array_in 3 {a b 3}
        } -result 6
        test function-enum-$type-5 "function enum $type array inout" -setup {
            cffi::enum delete *
            cffi::enum define E {a 1 b 2}
        } -body {
            testDll function ${type}_array_inout void [list n int arr [list $type\[3\] {enum E} inout]]
            set out {a 3 b}
            ${type}_array_inout 3 out
            set out
        } -result {b 4 3}
        test function-enum-$type-6 "function enum $type array out" -setup {
            cffi::enum delete *
            cffi::enum define E {a 1 b 2}
        } -body {
            testDll function int_array_count_copy void {arr_in {int[n_in] {enum E}} n_in int arr_out {int[n_out] out {enum E}} n_out int}
            int_array_count_copy {a b 5} 3 arr_out 4
            set arr_out
        } -result {a b 5 0}

        test function-enum-$type-error-0 "function enum $type - missing enum" -setup {
            cffi::enum delete *
        } -body {
            set fn ${type}_to_$type
            testDll function $fn $type [list inparam [list $type {enum E}]]
        } -result "Enum \"E\" not found or inaccessible. *Error defining function *" -match glob -returnCodes error
        test function-enum-$type-error-1 "function enum $type - missing enum entry" -setup {
            cffi::enum delete *
            cffi::enum define E {x 1 a 42}
        } -body {
            set fn ${type}_to_$type
            testDll function $fn $type [list inparam [list $type {enum E}]]
            $fn z
        } -result {expected integer but got "z"} -returnCodes error

        test function-enum-scope-$type-0 "function enum scope local $type" -setup {
            cffi::enum delete *
            cffi::enum define E {x 1 a 42}
            namespace eval :: {
                cffi::enum delete *
                cffi::enum define E {x 2 y 3}
            }
        } -body {
            set fn ${type}_to_$type
            testDll function $fn $type [list inparam [list $type {enum E}]]
            list [$fn x]
        } -result 1
        test function-enum-scope-$type-1 "function enum scope global $type" -setup {
            cffi::enum delete *
            cffi::enum define E {x 1 a 42}
            namespace eval :: {
                cffi::enum delete *
                cffi::enum define D {x 2 y 3}
            }
        } -body {
            set fn ${type}_to_$type
            testDll function $fn $type [list inparam [list $type {enum D}]]
            list [$fn x]
        } -result 2
    }

    ###
    # bitmask
    foreach type $intTypes {
        test function-bitmask-$type-0 "function bitmask $type" -setup {
            cffi::enum delete *
            cffi::enum define E {a 1 b 2}
        } -body {
            set fn ${type}_to_$type
            testDll function $fn $type [list inparam [list $type bitmask {enum E}]]
            $fn [list a 4 b]
        } -result 7
        test function-bitmask-$type-1 "function bitmask $type byref" -setup {
            cffi::enum delete *
            cffi::enum define E {a 1 b 2}
            set fn ${type}_byref
            testDll function $fn $type [list inparam [list $type bitmask {enum E} byref]]
        } -body {
            $fn {b 4 a}
        } -result 9
        test function-bitmask-$type-2 "function bitmask $type inout" -setup {
            cffi::enum delete *
            cffi::enum define E {a 1 b 2}
            set fn ${type}_inout
            testDll function $fn $type [list inoutparam [list $type {enum E} bitmask inout]]
        } -body {
            set param {4 b a}
            list [$fn param] $param
        } -result {9 8}
        test function-bitmask-$type-3 "function bitmask $type array inout" -setup {
            cffi::enum delete *
            cffi::enum define E {a 1 b 2 c 4}
        } -body {
            testDll function ${type}_array_inout void [list n int arr [list $type\[3\] {enum E} inout bitmask]]
            set var {a 3 {b 8 c}}
            ${type}_array_inout 3 var
            set var
        } -result {{b 2} {c 4} {a b c 15}}
        test function-bitmask-$type-4 "function bitmask $type out" -setup {
            cffi::enum delete *
            cffi::enum flags E {a b c}
            set fn ${type}_out
            testDll function $fn [list $type {enum E} bitmask] [list inparam $type outparam [list $type out {enum E} bitmask]]
        } -body {
            list [$fn 1 x] $x [$fn 2 y] $y [$fn 6 z] $z
        } -result [list {a b 3} {b 2} {c 4} {a b 3} 8 {a b c 7}]
        test function-bitmask-$type-5 "function bitmask $type retval" -setup {
            cffi::enum delete *
            cffi::enum flags E {a b c}
            set fn ${type}_out
            testDll function $fn [list $type nonzero] [list inparam $type outparam [list $type retval {enum E} bitmask]]
        } -body {
            list [$fn 1] [$fn 2] [$fn 6]
        } -result [list {b 2} {a b 3} {a b c 7}]

    }

    ###
    # struct -clear
    test function-struct-clear-0 "Call function with struct -clear" -body {
        cffi::Struct create S {c uchar ll longlong s {short {default 2}}} -clear
        testDll function {structCheck fn} int {s {struct.S byref} c schar ll longlong s short}
        fn {c 1} 1 0 2
    } -cleanup {
        rename fn {}
        S destroy
    } -result 1

    ###
    # scopes - aliases
    test function-alias-scope-0 "local alias overrides global" -setup {
        namespace eval :: {
            cffi::alias delete *
            cffi::alias define RET int
            cffi::test::testDll function int_to_int RET {i int}
        }
        namespace eval ::ns {
            cffi::alias delete *
            cffi::alias define RET uchar
            cffi::test::testDll function int_to_int RET {i int}
        }
    } -body {
        list [::int_to_int 65537] [::ns::int_to_int 65537]
    } -result {65537 1}
    test function-alias-scope-1 "alias falls back to global" -setup {
        namespace eval :: {
            cffi::alias delete *
            cffi::alias define RET int
        }
        namespace eval ::ns {
            cffi::alias delete *
            cffi::test::testDll function int_to_int RET {i int}
        }
    } -body {
        list [::ns::int_to_int 65537]
    } -result 65537
}

${NS}::test::testDll destroy

::tcltest::cleanupTests
namespace delete ${NS}::test
