Announcement

Collapse
No announcement yet.

ASCIIZ empty parameter bug (or feature?)

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    ASCIIZ empty parameter bug (or feature?)

    I was getting one of those very elusive GPF-type crashes. It
    seemed like the bug could be in the VB program that was
    calling the DLL. Or it could be some new lines of code that I
    had just added. A likely cause for that type of error could
    have been an array somewhere being referenced out of bounds, or
    invalid pointer usage. But after narrowing it down, I found
    that passing an empty string ("") to a routine that takes
    an ASCIIZ argument causes a "Subscript / Pointer out of range"
    error (error #9) when the argument is used. I didn't see
    anything in the documentation or forums which would explain why
    this happens. Try the following code, and ERR returns error #9.

    Code:
    $Compile Exe
    $Debug Error On
    
    Sub Test(Arg As Asciiz)
       Temp$ = UCase$(Arg)
       MsgBox Str$(Err)
    End Sub
    
    Function PbMain As Long
       Call Test("")
    End Function
    In the actual DLL component, it crashed in a totally unrelated
    area of code, and it was pretty tricky to track down.

    ------------------
    Daniel Corbier
    UCalc Fast Math Parser
    http://www.ucalc.com
    Daniel Corbier
    uCalc Fast Math Parser
    uCalc Language Builder
    sigpic

    #2
    When the string is empty, VB will be passing a null-pointer to PowerBASIC (you passed the dynamic string BYVAL, right?). As you probably know, null pointers are a no-no.

    I've not tried it, but if you should be able to test the ASCIIZ pointer (something like IF ISFALSE VARPTR(azstr) THEN...) and react accordingly.

    Alternatively, you may prefer to pass the dynamic string BYREF from VB, and receive the string BYREF in PowerBASIC instead. This will work fine even if the string is empty, since a pointer to the string handle is passed instead of a null pointer.

    In summary, this is not a bug, but an implementation/usage issue.



    ------------------
    Lance
    PowerBASIC Support
    mailto:[email protected][email protected]</A>
    Lance
    mailto:[email protected]

    Comment


      #3
      Thanks Lance. Yes I was passing the string from VB (ByVal), but
      a call to that function is placed from within the DLL as well. I
      was thinking of making sure that the value being passed had at
      least a space (" ") in it. But the VARPTR idea you suggested
      looks better, as it should handle any kind of string passed from
      any external program.

      I do hope that things like this will be clearly discussed in the
      next help file, so that we don't have to learn it the hard way.
      It would be appropriate in Appendix B, where it explains how
      to pass strings from VB to PB (There are probably other places
      where it can be mentioned as well).

      Also, is there no way for the compiler (future version) to
      gracefully do a conversion from null to an empty string, when
      this ASCIIZ variable is assigned to a non-ASCIIZ string, or when
      it's used in a string expression, and spare the unsuspecting
      programmer from this scenario?

      While on this topic, here's another related wishlist item. I
      have Exported functions in my DLL which are also called from
      routines within the DLL. The compiler always requires that I
      add "ByCopy" when I'm passing a regular string to a function
      which takes ASCIIZ. I would like the compiler to just pass it
      by copy for me, without me having to explicitely type ByCopy
      all the time.

      Also, support for a centralized error trapping routine of some
      sort would be a great time saver. Having to add "On Error Goto"
      to several functions until you norrow down errors like the above
      can get tedious (especially when the error might be locately
      elsewhere from where you'd think of looking).

      And speaking of On Error Goto I noticed that the compiler does
      not let me give the label a name that exists in another
      unrelated routine as a variable name. I thought labels were
      local.

      On the other hand I really need a way to use labels from other
      routines, like I was able to do with PB 5. I won't actually be
      jumping from one routine to another but I just need to get the
      CodePtr address from another routine, as described in a previous
      message at http://www.powerbasic.com/support/fo...-4-000497.html
      This is a major wish for me. For now, I'm weighing the option
      of compiling a particular project with PB 5 just for that.

      ------------------
      Daniel Corbier
      UCalc Fast Math Parser
      http://www.ucalc.com
      Daniel Corbier
      uCalc Fast Math Parser
      uCalc Language Builder
      sigpic

      Comment


        #4
        Also, is there no way for the compiler (future version) to
        gracefully do a conversion from null to an empty string, when
        this ASCIIZ variable is assigned to a non-ASCIIZ string, or when
        it's used in a string expression, and spare the unsuspecting
        programmer from this scenario?
        It is not really the place of the compiler to start altering what is being passed to your code... afterall, you may actually want to be passed a nul-pointer at some stage!

        While on this topic, here's another related wishlist item. I
        have Exported functions in my DLL which are also called from
        routines within the DLL. The compiler always requires that I
        add "ByCopy" when I'm passing a regular string to a function
        which takes ASCIIZ. I would like the compiler to just pass it
        by copy for me, without me having to explicitely type ByCopy
        all the time.
        You can simply enclose that parameter in parenthesis to force it to be evaluated as an expression (2 characters instead of 7), and hence gets passed BYCOPY. What you are asking is for the compiler to anticipate how you want the string passed, and again, this is not really the job of the compiler.

        Also, support for a centralized error trapping routine of some
        sort would be a great time saver. Having to add "On Error Goto"
        to several functions until you norrow down errors like the above
        can get tedious (especially when the error might be locately
        elsewhere from where you'd think of looking).
        The problem is that a global error trap adds a lot of overhead and serious complications to the app, mostly because a global error trap is effectively out of scope of the code that would have triggered the error. Threads make the problem even more complex, since more than one thread could trigger an error trap, so the error trap would need to be thread-safe!)

        Basically (no pun intended), this gets incredibly messy incredibly quickly. R&D have discussed this before and have said that it is unlikely to be implemented unless someone can come up with a significant reason for such a feature.

        And speaking of On Error Goto I noticed that the compiler does
        not let me give the label a name that exists in another
        unrelated routine as a variable name. I thought labels were
        local.
        Labels are indeed local, but they must also be unique.

        On the other hand I really need a way to use labels from other
        routines, like I was able to do with PB 5.
        I understand, but this behavior in 6.0 was intentional, and not likely to be relaxed any time soon.
        However, the easiest way around the problem would be have the target sub/function sort out the codeptr address array when first run, so there would be only the very small overhead of one numeric test (over and above the time-critical code):
        ie,
        Code:
        GLOBAL ProcArray() AS LONG
        SUB MySub
          STATIC Init&
          IF ISFALSE Init& THEN
             ProcArray(1) = CODEPTR(label1)
             ProcArray(2) = CODEPTR(label2)
             ...
             Init& = %TRUE
             EXIT SUB
          END IF
         
          ... normal code goes here, ie:
         
        Label1:
          GOSUB DWORD ProcArray(n&)
         
        Label2:
         
        END SUB
        ------------------
        Lance
        PowerBASIC Support
        mailto:[email protected][email protected]</A>
        Lance
        mailto:[email protected]

        Comment


          #5
          Lance, after reading your first response, I printed VarPtr(Arg)
          with MsgBox to verify that it returned a 0 for empty strings.
          I thought I now had a solution on hand, until I actually tried
          to implement VarPtr(Arg) with an IF statement. It seems like
          VarPtr(Arg) triggers an error when Arg is null in this context,
          which seems strange. Here's what I tried:
          Code:
          $Compile Exe
          $Debug Error On
          
          Sub Test(Arg As Asciiz)
             If VarPtr(Arg) = 0 Then Temp$ = "" Else Temp$ = Arg
                   
             MsgBox Str$(Err) ' Still returns an error
          End Sub
          
          Function PbMain As Long
             Call Test("")
          End Function
          I also got the same result using ISFALSE.

          It is not really the place of the compiler to start altering what is being passed to your code... afterall, you may actually want to be passed a nul-pointer at some stage!
          I don't need for the compiler to modify the argument that's being
          passed. What I was thinking of was for the compiler to check for
          null when an ASCIIZ string is mixed with a regular string, as in:

          LocalString$ = AsciiZArg

          If the compiler won't do it automatically, perhaps PB can add a
          CSTR() function which would work just like CBYT(), CCUR(), etc...
          So if necessary, I can do this:

          LocalString$ = CSTR(AsciiZArg)

          And it would take care of converting the null value to a proper
          empty string.

          You can simply enclose that parameter in parenthesis to force it to be evaluated as an expression (2 characters instead of 7), and hence gets passed BYCOPY. What you are asking is for the compiler to anticipate how you want the string passed, and again, this is not really the job of the compiler.
          If there were several options for the way that variable could be
          passed, I'd understand, but the compiler does not let me pass this
          string argument ByRef or ByVal. There's no other apparent option
          other than BYCOPY. The compiler stops compiling at that line and
          even suggests that I use BYCOPY (as though I had a choice).

          The problem is that a global error trap adds a lot of overhead and serious complications to the app, mostly because a global error trap is effectively out of scope of the code that would have triggered the error. Threads make the problem even more complex, since more than one thread could trigger an error trap, so the error trap would need to be thread-safe!)
          Basically (no pun intended), this gets incredibly messy incredibly quickly. R&D have discussed this before and have said that it is unlikely to be implemented unless someone can come up with a significant reason for such a feature.
          I hope I can make a convincing case for this. For me a very
          significant reason is the time involved in tracking down tricky
          errors which may be located nowhere near the apparent source
          of the problem.

          1. Narrowing down an error by placing On Error Goto and
          accompanying code to each line until the error is found is very
          time consuming.

          2. The code which accompanies On Error Goto is itself prone to
          adding more errors to the program. When adding these lines
          everywhere you might type it in correctly for most functions,
          and add an undetected typo in just one of them.

          3. Speed and file size are not extremely important when debugging.
          I understand that PB wants to make a compiler that's very fast.
          Speed is indeed very important for me in the non-debug version
          of my DLL. But when I'm debugging, micro-seconds are irrelevant.
          (Actually, the current debugger is a little slower than I excpect.
          But I live with it).

          4. With Multiple error handlers, one is unlikely to uncover bugs
          that would be revealed with a centralized error handler. In a
          case like the above with ASCIIZ strings causing GPFs, I was lucky
          that this error happened to be in a routine where arrays were
          being defined and used, but even then I took a lot of time
          because I was after arrays. Had it been caused by a small
          innocent-looking function with no arrays or pointers in it, it
          might not have occurred to me to check for errors there at all
          (at least not before I would start growing gray hairs

          By the way, you can restrict global error-handling to files
          that are compiled as stand-alone EXEs. When debugging for things
          like this I simply load my DLL code into PB/CC, and run it as a
          stand-alone (conditional $If statements with alternative PBMain
          program for when I'm running it in PB/CC makes this easy to do).
          You can also simply document the multi-thread limitation. Even
          a rudimentary global error handler would be an incredible time
          saver, and it would also catch obscure bugs that me not ever be
          cought otherwise.

          Labels are indeed local, but they must also be unique.
          I'm not sure I understand how this rule is implemented. I find
          that I can use the same label name ErrorHandler in
          different functions (it would be terrible if I had to have a
          unique error handler label for each routine). But then if I
          don't want to type a long word like that and just name
          the label x, then when I compile, it gives a
          Duplicate name error because I happen to have a variable
          named x somewhere in a different function. I can
          understand the need for a unique label name within the same
          function but I don't see why the compiler should complain if the
          label shares the same name as an unrelated variable located in a
          different function.

          However, the easiest way around the problem would be have the target sub/function sort out the codeptr address array when first run, so there would be only the very small overhead of one numeric test (over and above the time-critical code):
          You're right. I tried it, and the overhead is minimal. The
          speed difference was so small that it was hard to measure. I
          had implemented another equally effective work-around but assumed
          that it must have a speed penalty. However, one speed-related
          feature that I still hope for is _fastcall. That one showed a
          very remarkable speed difference when I measured earlier this year.



          ------------------
          Daniel Corbier
          UCalc Fast Math Parser
          http://www.ucalc.com
          Daniel Corbier
          uCalc Fast Math Parser
          uCalc Language Builder
          sigpic

          Comment

          Working...
          X
          😀
          🥰
          🤢
          😎
          😡
          👍
          👎