0

I have the following code that sometimes returns an error when trying to create a new directory. Sometimes when that directory doesn't exist, it throws an error and does not create the directory. I would like to know why?

DECLARE DYNAMIC LIBRARY "kernel32"
    FUNCTION CreateDirectoryA% (F$, X$)
    FUNCTION GetLastError& ()
END DECLARE
F$ = "TEMPX" + CHR$(0) ' new directory to create
x = CreateDirectoryA(F$, CHR$(0))
IF x = 0 THEN
    IF GetLastError = &H3E6 THEN
        PRINT "Invalid access to memory location."
    END IF
END IF

This code was written in QB64.

eoredson
  • 1,167
  • 2
  • 14
  • 29

3 Answers3

1

What you're passing as the lpSecurityAttributes parameter of CreateDirectoryA is a string of length 0; strings still have memory addresses, which means you're passing a non-null pointer. This is interpreted by CreateDirectoryA as a pointer to a SECURITY_ATTRIBUTES structure with its first byte set to 0. The rest of the bytes in the structure are memory you haven't initialized, meaning the structure's first field nLength on a little endian machine could be something like 0xFFFFFF00, which is 4294967040—an invalid size. The lpSecurityDescriptor field of that structure is presumably filled with bytes as well (possibly accessible by your program, but perhaps not), so this could be causing the issue as well.

You can remedy this easily:

' X%& may also be written X AS _OFFSET.
FUNCTION CreateDirectoryA% (F$, BYVAL X%&)

...

x = CreateDirectoryA(F$, 0)

This results in passing a NULL pointer properly. The BYVAL keyword is necessary, else you'll be passing a pointer to the _OFFSET value (because everything in QB64 is passed by pointer unless you use the BYVAL keyword; strings and user-defined types cannot be passed BYVAL).

  • Thank you! That may have solved the problem. Thumbs up. – eoredson Jul 11 '17 at 04:13
  • And the CreateDirectory function call points to the SECURITY_ATTRIBUTES structure. And the SECURITY_ATTRIBUTES structure points to the SECURITY_DESCRIPTOR structure. And the... – eoredson Jul 12 '17 at 01:56
  • A security descriptor includes: An owner security identifier (SID) A primary group SID A discretionary access control list (DACL) A system access control list (SACL) – eoredson Jul 12 '17 at 02:04
  • 1
    @eoredson Exactly. However, that is an optional parameter, meaning `NULL` can be passed for the default security descriptor (I think it's inherited from the parent directory?) To do that, you need to declare the function with an `_OFFSET` passed `BYVAL` as the second param and use 0 as the argument. –  Jul 12 '17 at 02:27
0

This code forces the creation of a directory when the exception x3E6 happens but does explain why the error happens in the first place:

DECLARE DYNAMIC LIBRARY "kernel32"
    FUNCTION CreateDirectoryA% (F$, X$)
    FUNCTION GetLastError& ()
END DECLARE
DO
    F$ = "TEMPX" + CHR$(0) ' new directory to create
    x = CreateDirectoryA(F$, CHR$(0))
    IF x THEN ' successful create
        EXIT DO
    END IF
    IF x = 0 THEN
        REM Test for Invalid access to memory location.
        IF GetLastError = &H3E6 THEN
            _DELAY .2
        ELSE
            EXIT DO
        END IF
    END IF
LOOP
eoredson
  • 1,167
  • 2
  • 14
  • 29
  • Odd. I would think `lpSecurityAttributes` (`X$` in your declaration) would be something like `_OFFSET` (`X%&`) rather than a string. `CHR$(0)` implies you have a string containing a single byte with value 0, so I'd pass `0%&` to the function, resulting in a `NULL` pointer. The `CHR$(0)` might be the cause of your "sometimes" issue. Then again, I haven't used Windows in a while, so maybe I'm wrong. I'm surprised there isn't yet a resource that maps various C/Windows API data types to QB64 types… –  Jul 09 '17 at 05:52
  • All strings passed to windows libraries are necessarily null terminated. The null in the second parameter is required. The first parameter declares a pointer to the function as it should. – eoredson Jul 10 '17 at 01:29
  • I'm referring to a NULL pointer, not a null-terminated string. `lpSecurityAttributes` is a pointer to a structure, not a string, but I've seen you post about this sort of stuff before, so perhaps you know something I don't. After all, I don't work with QB64 anymore, so anything I say may be based on rusty memories. :P –  Jul 10 '17 at 01:33
  • In CreateDirectory MSDN it declares: "If lpSecurityAttributes is NULL, the directory gets a default security descriptor." – eoredson Jul 10 '17 at 01:48
0

Then I ran into a problem creating subdirectories in a path, so I wrote this:

DECLARE DYNAMIC LIBRARY "kernel32"
    FUNCTION CreateDirectoryA% (F$, BYVAL X&&)
    FUNCTION GetLastError& ()
END DECLARE
DO ' get directory
    PRINT "Dir";: INPUT Directory$
    IF Directory$ = "" THEN END
    GOSUB CreateDir
LOOP
END
CreateDir:
' construct path
Directory$ = RTRIM$(Directory$)
IF RIGHT$(Directory$, 1) <> "\" THEN
    Directory$ = Directory$ + "\"
END IF
' create path
x = 0
Next.Dir = INSTR(Directory$, "\")
DO
    IF Next.Dir = False THEN
        EXIT DO
    END IF
    SubDir$ = LEFT$(Directory$, Next.Dir - 1) ' \tempx\t1\t2\t3\
    Next.Dir = INSTR(Next.Dir + 1, Directory$, "\")
    ' make directory name
    IF LEN(SubDir$) THEN
        ' create directory
        f$ = SubDir$ + CHR$(0)
        x = CreateDirectoryA(f$, 0)
        ' check error
        IF x = 0 THEN
            IF GetLastError& = &HB7 THEN ' ignore already exists
                ' nul
            ELSE
                EXIT DO
            END IF
        END IF
    END IF
LOOP
IF x = 0 THEN
    PRINT "Error x"; HEX$(GetLastError&)
ELSE
    PRINT "Directory created."
END IF
RETURN

This way if you specify a path that does not exist, it will create the entire pathname. For example, \Temp\t1\t2\t3 if \Temp does not exist.

eoredson
  • 1,167
  • 2
  • 14
  • 29
  • Because if you don't then you get an error 0x3 which is a "The system cannot find the path specified." – eoredson Jul 18 '17 at 04:21