Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

SQLDrivers() Demo

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

  • SQLDrivers() Demo

    for my sixth example program in 'beginning win32 sdk style programming example series' here in
    the source code forum...

    http://www.powerbasic.com/support/pb...ad.php?t=25174

    i used the odbc sqldrivers() function to amass database driver information to load a text box
    that covers the entire extent of a display form/dialog. the example there was somewhat elementary,
    except for the odbc sqldrivers() call, which is rather involved. it occurred to me that the flow
    of the series itself and my attempts at providing clear explanations would be best served if i
    posted the sqldrivers() explanations in a seperate post not part of the 'beginning ...' series.
    that way for learners who are just interested in the mechanics of getting multiple dialog projects
    running, or for those who are just interested in seeing how to do simple scrollable information
    display, the complexities of the sqldrivers() function could be relegated to that of a 'black box',
    out of which information faithfully if not somewhat mysteriously flowed. the comment thread for the
    sdk series is at...

    http://www.powerbasic.com/support/pb...ad.php?t=14429

    ...and comments on this thread could also go there. so in this seperate post in a different
    thread i'll attempt to give a blow by blow and detailed explanation for the sqldrivers() function
    for those who are interested. below is the full code for an example that should run for both the
    console compiler and pb windows (uses a display macro). note that i abstracted from the odbc
    includes at:

    http://www.powerbasic.com/files/pub/...e/pbodbc30.zip

    the eight equates and four functions necessary to make this example work. sqldrivers() is in
    odbc32.dll, so this setup is necessary for the program to run. in this first and full
    program they are listed right after the win32api.inc include. in the explanatory programs after-
    wards i didn't want to include them in every program so as to save space, so i am asking you to
    copy them to an include file named odbcincs.inc. you'll note that include in the programs after
    this one.

    Code:
    ''sqldrivers.bas     'tested with cc4.01,4.02,4.03,8.01,8.02,8.03
    #compile exe
    #register none
    #dim all
    #include "win32api.inc"  'main windows include file.
    '
    'save to odbcincs.inc. for programs after this one.  these equates and declares are from
    'sqltypes.inc, sql32.inc and sqlext32.inc.
    %sql_null_handle              = 0&
    %sql_handle_env               = 1
    %sql_attr_odbc_version        = 200
    %sql_ov_odbc3                 = 3???
    %sql_is_integer               = (-6)
    %sql_fetch_next               = 1
    %sql_no_data                  = 100
    %sql_error                    = -1
    '
    declare function sqlallochandle lib "odbc32.dll" alias "sqlallochandle" _
    ( _
      byval handletype          as integer,_
      byval inputhandle         as dword,_
      byref outputhandle        as dword _
    ) as integer
    '
    declare function sqlsetenvattr lib "odbc32.dll" alias "sqlsetenvattr" _
    ( _
      byval environmenthandle   as dword,_
      byval attribute           as long,_
      byref value               as any,_
      byval stringlength        as long _
    ) as integer
    '
    declare function sqldrivers lib "odbc32.dll" alias "sqldrivers" _
    ( _
      byval henv                as dword,_
      byval fdirection          as word,_
      byref szdriverdesc        as asciiz,_
      byval cbdriverdescmax     as integer,_
      byref pcbdriverdesc       as integer,_
      byref szdriverattributes  as asciiz,_
      byval cbdrvrattrmax       as integer,_
      byref pcbdrvrattr         as integer _
    ) as integer
    '
    declare function sqlfreehandle lib "odbc32.dll" alias "sqlfreehandle" _
    ( _
      byval handletype          as integer,_
      byval thehandle           as dword _
    ) as integer
    'end of odbcincs.inc.
    '
    macro display(msg,fp)  'if program is running in console compiler, output goes to stdout of
      #if %def(%pb_cc32)   'console window.
          stdout msg
      #else
          print #fp, msg   'otherwise, to a file which will be shellexecut()'ed at program termination.
      #endif
    end macro
    '
    function pbmain() as long
      local ilen1 as integer, ilen2 as integer, fn as integer
      local szdriverdes as asciiz*64, szdriverattr as asciiz*256
      local sztextfile as asciiz*256, szcurdir as asciiz*256
      local ptrbyte as byte ptr
      local strarr() as string
      local henvr as dword
      register i as long
      '
      #if %def(%pb_cc32)
          console set screen 400, 80
      #else
          sztextfile=curdir$ & "\" & "output.txt"
          fn=freefile
          open sztextfile for output as #fn
      #endif
      if sqlallochandle(%sql_handle_env,%sql_null_handle,henvr)<>%sql_error then
         call sqlsetenvattr(henvr,%sql_attr_odbc_version,byval %sql_ov_odbc3,%sql_is_integer)
         while sqldrivers(henvr,%sql_fetch_next,szdriverdes,64,ilen1,szdriverattr,256,ilen2)<>%sql_no_data
           display(szdriverdes,fn)  : display(chr$(13),fn)
           ptrbyte=varptr(szdriverattr)  'get address of start of buffer containing attribute/value pairs.
           decr ilen2                    'convert from one based count to zero based count, then move
           for i=0 to ilen2              'through buffer converting any null bytes encountered to commas.
             if @ptrbyte[i]=0 then       'null bytes are 0 and commas are chr$(44), or just 44 here.  this
                @ptrbyte[i]=44           'technique uses a base pointer and pointer arithmetic to do its job.
             end if                      'a double null byte sequence exists at end of characters in buffer,
           next i                        'one of which was included in count in ilen2.  this would have been
           @ptrbyte[ilen2]=0             'converted to a trailing comma by the code above, so lets remove it.
           redim strarr(parsecount(szdriverattr)-1)
           parse szdriverattr,strarr()
           for i=0 to ubound(strarr,1)
             display(strarr(i),fn)
           next i
           display(chr$(13,10) & chr$(13),fn)
         loop
         call sqlfreehandle(%sql_handle_env,henvr)
      else
         display("odbc function call failure.  couldn't allocate environment handle!",fn)
      end if
      #if %def(%pb_cc32)
          waitkey$
      #else
          close #fn
          szcurdir=curdir$
          call shellexecute(0,"open",sztextfile,byval 0,szcurdir,%sw_showdefault)
      #endif
      '
      pbmain=0
    end function
    if you ran the example you will have noted that for each driver there is a verbose description,
    such as, 'sql server' or 'microsoft access driver (*.mdb)', followed by a blank line and half a
    dozen or so attribute/value pairs. on my system the first driver for which information is provided
    is the sql server driver like so...

    sql server

    usagecount=5
    sqllevel=1
    fileusage=0
    driverodbcver=02.50
    connectfunctions=yyy
    apilevel=2
    cptimeout=60

    and this format of data is repeated for all drivers on the system on which the program is
    running. now the thing is, this format of the data was created by the program, as it makes sense
    to view it in that manner. sqldrivers() does not present it in that format. the format
    sqldrivers() presents the data in is one where a string buffer provided by the program for the
    driver description is filled with that description, and another buffer provided for the
    attribute/value pairs is filled with that information - all in one asciiz character string buffer
    with unchanging address and each attribute/value pair seperated by a null byte. in addition, the
    odbc functions always return in a seperate buffer the numbers of bytes returned in adjacent
    character string buffers. our work in this example will be to use all the information provided by
    sqldrivers() so as to format it into the readily readable format that you see when you run the
    program.

    so lets begin. the first thing you always have to do when using odbc functions is set up an
    odbc environment - sometimes called a 'workspace' in other contexts, and set the odbc version. a
    handle to an odbc environment is first obtained by calling the following function:

    sqlallochandle(%sql_handle_env, %sql_null_handle, henvr)

    odbc functions never return database data through return values, but rather through function
    parameters. note that before this call in pbmain() henvr was dimensioned as a local dword variable
    but was not initialized before its use in this function, other than its default initialization to
    zero by the compiler. the reason for this is that after the function call, if the function call
    succeeds, henvr will contain a valid odbc handle to an environment. in terms of actual return
    values, odbc functions return success/failure codes, as can be observed in pbmain() above where you
    can see i tested for %sql_success. %sql_success is defined in the equates above as zero, and can be
    thought of a zero errors.

    armed with a valid henvr we can set the odbc version through...

    call sqlsetenvattr(henvr, %sql_attr_odbc_version, byval %sql_ov_odbc3, %sql_is_integer)

    after that we're ready to get down to business. at this point i'd recommend you obtain
    microsoft's actual documentation for sqldrivers() as we are ready to call it. there are over a
    thousand pages of documentation on the odbc api but the documentation on sqldrivers() is only a few
    pages or so and is located at...

    http://msdn2.microsoft.com/en-gb/library/ms712400.aspx

    referring either to that microsoft documentation, or to the powerbasic declare above in
    odbcincs.inc, you can see the first parameter is the handle to the environment we obtained above.
    the second parameter is an equate equaling either 1 or 2 depending on which way you want to move
    through the database driver information. the %sql_fetch_next is most useful and we'll use it here.
    after that are six more complicated looking parameters but in actuality the six refer to only two
    pieces of real data, and they are the character string driver description name (szdriverdes) such
    as 'sql server', and the driver attribute/value pairs (szdriverattributes) as seen and described
    above.

    to set up the function call the programmer has to provide memory owned by the program into
    which the function can place the driver and attribute data. programmers often refer to such memory
    as buffers. note in pbmain() the variable declarations...

    local szdriverdes as asciiz*64, szdriverattr as asciiz*256

    those variables are the buffers into which sqldrivers() will repeatedly place driver
    description and attribute/value information until the list of database information is exhausted.
    i can't overemphasize enough that these are output parameters meaning that information is not fed
    to the function through them but rather extracted from it by the programmer and his/her app. if
    you examine the microsoft documentation you will see in the extensive write up on the function that
    they clearly mark each parameter as either an input parameter, an output parameter, or an
    input/output parameter (yes, you can have those too! but in sqldrivers() they are either/or).

    another point that just occurred to me is that if you are coming from a programming background
    limited to the basic family of languages, the whole idea of a string data type such as asciiz and
    the idea of creating a 'buffer owned by the application' might seem peculiar to you. what is going
    on here is actually fairly profound, and consists of the fact that in the entire world of
    computerdom, there are two entirely different and opposing ways of representing strings (i fear
    this is going to be a digression!).

    let me give an example. suppose in a powerbasic program you wrote you had a procedure where
    you needed an integer long and a string like so:

    local inumber as long
    local strcompanyname as string

    and then you assigned data to the variables like this...

    inumber = 17
    strcompanyname = "acme warehousing company, inc"

    the steps the compiler must take to compile the statement with the integer are considerably
    different than the steps it must take with the string. in the case of the long the compiler must
    reserve a four byte piece of memory to store the '17', then move the binary representation of '17'
    to that location. in the case of the string the compiler must generate code to first measure the
    string, then do an api call to perform a memory allocation for the length of the string, after that
    create a string descriptor that holds information on the string such as its length, and finally
    actually move the bytes of the string's characters to the buffer it allocated to hold the string.
    and this all occurs transparently to you the programmer.

    when you enter the world of the windows api or the odbc api you are entering a world where
    string handling doesn't work that way. in fact, if you even search for the word 'string' in the
    documentation for any of the api functions that involve what we think of as string data you will be
    hard pressed to find it (in sqldrivers() i found the word string exactly one time). you will
    constantly see references instead to 'buffers' and 'pointer to a buffer'. the reason for this is
    also fairly profound and far reaching. in the assembler and c code in which windows was written, in
    the assembler and c code in which many operating systems were written, there is no string data type.
    when c++ eventually came on the scene after both unix and windows were already written a c++ string
    data type was created using c++ classes along the lines of basic implementations. in later
    additions to the windows api you will find other kinds of 'strings' similiar to basic strings, but
    not in the original libraries dating from the early days of windows.

    digression over and back to sqldrivers(). below is a simplified version of the program in
    which there is only one call to the sqldrivers() function, as i eliminated its placement within the
    while loop construct. what we'll do is simply call the function one time after setting up a few
    necessary parameters, and then we'll examine in detail what data was placed into the output
    parameters by the single function call. so copy the following code to a new editor window, but
    make sure both includes are accessable to the program from wherever on your computer it is running.
    remember to please place those odbc includes into the same directory as the source here. i'm
    speaking of the eight equates and four declares from the program at the top of this post.

    Code:
    'sqldrivers.bas
    #compile exe
    #register none
    #dim all
    #include "win32api.inc"   'main windows include file.
    #include "odbcincs.inc"   'highly limited abstract from sqltypes.inc, sql32.inc, and sqlext32.inc
    '
    macro display(msg,fp)     'if program is running in console compiler, output goes to stdout of
      #if %def(%pb_cc32)      'console window.
          stdout msg
      #else
          print #fp, msg  'otherwise, to a file which will be shellexecut()'ed at program termination.
      #endif
    end macro
    '
    function pbmain() as long
      local ilen1 as integer, ilen2 as integer, fn as integer
      local szdriverdes as asciiz*64, szdriverattr as asciiz*256
      local sztextfile as asciiz*256, szcurdir as asciiz*256
      local ptrbyte as byte ptr
      local strarr() as string
      local henvr as dword
      register i as long
      '
      #if %def(%pb_cc32)
          console set screen 400, 80
      #else
          sztextfile=curdir$ & "\" & "output.txt"
          fn=freefile
          open sztextfile for output as #fn
      #endif
      if sqlallochandle(%sql_handle_env,%sql_null_handle,henvr)<>%sql_error then
         call sqlsetenvattr(henvr,%sql_attr_odbc_version,byval %sql_ov_odbc3,%sql_is_integer)
         call sqldrivers(henvr,%sql_fetch_next,szdriverdes,64,ilen1,szdriverattr,256,ilen2)
         display("szdriverdes         = " & szdriverdes,fn)
         display("len(szdriverdes)    = " & trim$(str$(len(szdriverdes))),fn)
         display("ilen1               = " & trim$(str$(ilen1)),fn)
         display(chr$(13),fn)
         display("szdriverattr        = " & szdriverattr,fn)
         display("len(szdriverattr)   = " & trim$(str$(len(szdriverattr))),fn)
         display("ilen2               = " & trim$(str$(ilen2)),fn)
         call sqlfreehandle(%sql_handle_env,henvr)
      else
         display("odbc function call failure.  couldn't allocate environment handle!",fn)
      end if
      #if %def(%pb_cc32)
          waitkey$
      #else
          close #fn
          szcurdir=curdir$
          call shellexecute(0,"open",sztextfile,byval 0,szcurdir,%sw_showdefault)
      #endif
      '
      pbmain=0
    end function
    Code:
    below is output from above program:
    =====================================
    szdriverdes         = sql server
    len(szdriverdes)    = 10
    ilen1               = 10
    '
    szdriverattr        = usagecount=5
    len(szdriverattr)   = 12
    ilen2               = 101
    i have already explained what the first two parameters to sqldrivers() are. the third
    parameter, szdriverdes, is that asciiz fixed length string i started to describe when i launched
    myself into the digression about buffers. you should be able to figure out now what that third
    parameter is, and how it was used. sqldrivers() put its first driver description into it. on your
    machine it may not be sql server, but it will be a text description of some sort of driver. the
    fourth parameter is 64 and i just pulled that number out of the air, as my derivation of this
    function followed the steps i am outlining for you here, and i found that none of the text strings
    were very long. that was an input parameter, by the way. the fifth parameter is an output
    parameter and when sqldrivers() was called it returned to us the length of the text string output in
    the szdriverdes buffer. as you can see (and count), 'sql server' contains 10 characters and
    sqldrivers() informs us that it is returning 10 characters to us. so far so good.

    now, however, we get into trouble. the pattern and meaning of the next three parameters is
    exactly the same as for szdriverdes, and indeed we do get a string with an attribute/value pair, but
    we only seem to get the first one, and not the others. and the numbers definitely don't seem to
    match as they did for szdriverdes. the full set of information for the sql server driver from the
    first program was like so:

    usagecount=5
    sqllevel=1
    fileusage=0
    driverodbcver=02.50
    connectfunctions=yyy
    apilevel=2
    cptimeout=60

    it seems we only got the first line. the 'usagecount=5' certainly equals 12 characters, but
    according to the ilen2 output parameter of sqldrivers(), 101 characters of information were returned
    to us. if you read the 'comments' in microsoft's sqldrivers() documentation you'll find the
    following:

    each [attribute/value] pair is terminated with a null byte, and the entire list
    is terminated with a null byte (that is, two null bytes mark the end of the list).
    for example, a file-based driver using c syntax might return the following list of
    attributes:

    ("\0" represents a null character):

    fileusage=1\0fileextns=*.dbf\0\0

    those null bytes after the end of each attribute/value pair is fooling powerbasic into thinking
    it hit the end of the string after hitting a null byte after 'usagecount=5' (actually, that isn't
    exactly right. since a null byte marks the end of a string, that first null byte was in fact the end
    of the string). it isn't seeing those additional 89 bytes after that null. we own those bytes. they
    are saftly in 'our' buffer. but how do we get to them?

    about the fastest and easiest way to fix it is to set a pointer equal to the address of the first
    byte of the string, then loop through the string one byte at a time for 101 bytes (the count returned
    to us in ilen2, remember) testing all the way for null bytes. when a null byte is found, just stick
    some other less troublesome character in its place. in terms of just what particular character to
    stick there, just remember that all versions of basic love commas as seperators. there's just nothing
    basic loves more than seeing commas seperating bits of data. now c, that language has a fondness for
    white space, i.e., tabs, spaces, anything like that. but for basic, commas do it every time. so here
    is the next version of the program. try this...

    Code:
    #compile exe
    #register none
    #dim all
    #include "win32api.inc"   'main windows include file.
    #include "odbcincs.inc"   'highly limited abstract from sqltypes.inc, sql32.inc, and sqlext32.inc
    '
    macro display(msg,fp)     'if program is running in console compiler, output goes to stdout of
      #if %def(%pb_cc32)      'console window.
          stdout msg
      #else
          print #fp, msg  'otherwise, to a file which will be shellexecut()'ed at program termination.
      #endif
    end macro
    '
    function pbmain() as long
      local ilen1 as integer, ilen2 as integer, fn as integer
      local szdriverdes as asciiz*64, szdriverattr as asciiz*256
      local sztextfile as asciiz*256, szcurdir as asciiz*256
      local ptrbyte as byte ptr
      local strarr() as string
      local henvr as dword
      register i as long
      '
      #if %def(%pb_cc32)
          console set screen 400, 150
      #else
          sztextfile=curdir$ & "\" & "output.txt"
          fn=freefile
          open sztextfile for output as #fn
      #endif
      if sqlallochandle(%sql_handle_env,%sql_null_handle,henvr)<>%sql_error then
         call sqlsetenvattr(henvr,%sql_attr_odbc_version,byval %sql_ov_odbc3,%sql_is_integer)
         call sqldrivers(henvr,%sql_fetch_next,szdriverdes,64,ilen1,szdriverattr,256,ilen2)
         ptrbyte=varptr(szdriverattr)  'the varptr function returns the address of a variable.
         decr ilen2                    'so ptrbyte holds the address of the 256 byte long asciiz
         for i=0 to ilen2              'string buffer.  the ilen2 parameter holds the one based
           if @ptrbyte[i]=0 then       'count of characters returned.  i decremented it because
              @ptrbyte[i]=44           'in using a base pointer you are working with zero based
           end if                      'offsets from a starting point.  pointer subscript notation
         next i                        'is used to move through buffer substituting ','s for '0's.
         @ptrbyte[ilen2]=0             'lets also shorten the string and remove trailing comma.
         display("szdriverattr        = " & szdriverattr,fn)
         display("len(szdriverattr)   = " & trim$(str$(len(szdriverattr))),fn)
         display("ilen2               = " & trim$(str$(ilen2)),fn)
         call sqlfreehandle(%sql_handle_env,henvr)
      else
         display("odbc function call failure.  couldn't allocate environment handle!",fn)
      end if
      #if %def(%pb_cc32)
          waitkey$
      #else
          close #fn
          szcurdir=curdir$
          call shellexecute(0,"open",sztextfile,byval 0,szcurdir,%sw_showdefault)
      #endif
      '
      pbmain=0
    end function
    Code:
    below is output from above program:
    =====================================
    szdriverattr      = usagecount=5,sqllevel=1,fileusage=0,driverodbcver=02.50,connectfunctions=yyy,apilevel=2,cptimeout=60
    len(szdriverattr) = 100
    ilen2             = 100
    i believe we're making progress. now we've reached the point where we have all the
    information for one database driver. we have its name and the attribute/value pairs that
    describe it. all we need to do now is format the data into a readily readable list. the easiest
    way to do that is to use powerbasic's great parsecount and parse combination. if you've never used
    these you are in for a treat as they are wonderful and easy to use. parsecount returns the number
    of delimited fields in a string. if the delimiter is the comma all you have to do is call the
    function like so:

    inumberfields = parsecount(szdriverattr)

    for the example above inumberfields would be equal to seven. the parse statement can then be
    used to load a dynamically dimensioned array with the individual attribute/value pairs. so the
    sequence of operations is to dimension an array with parsecount-1 elements (parse starts filling the
    array at element zero, so we're back to reconciling zero based and one based counts), then execute
    the parse statement passing it first the string to be parsed and second the correctly dimensioned
    string array into which it will place the parsed strings. so now try this next iteration of the
    program...

    Code:
    #compile exe
    #register none
    #dim all
    #include "win32api.inc"   'main windows include file.
    #include "odbcincs.inc"   'highly limited abstract from sqltypes.inc, sql32.inc, and sqlext32.inc
    '
    macro display(msg,fp)     'if program is running in console compiler, output goes to stdout of
      #if %def(%pb_cc32)      'console window.
          stdout msg
      #else
          print #fp, msg      'otherwise, to a file which will be shellexecut()'ed at program termination.
      #endif
    end macro
    '
    function pbmain() as long
      local ilen1 as integer, ilen2 as integer, fn as integer
      local szdriverdes as asciiz*64, szdriverattr as asciiz*256
      local sztextfile as asciiz*256, szcurdir as asciiz*256
      local ptrbyte as byte ptr
      local strarr() as string
      local henvr as dword
      register i as long
      '
      #if %def(%pb_cc32)
          console set screen 400, 150
      #else
          sztextfile=curdir$ & "\" & "output.txt"
          fn=freefile
          open sztextfile for output as #fn
      #endif
      if sqlallochandle(%sql_handle_env,%sql_null_handle,henvr)<>%sql_error then
         call sqlsetenvattr(henvr,%sql_attr_odbc_version,byval %sql_ov_odbc3,%sql_is_integer)
         call sqldrivers(henvr,%sql_fetch_next,szdriverdes,64,ilen1,szdriverattr,256,ilen2)
         display(szdriverdes,fn)
         display(chr$(13),fn)
         ptrbyte=varptr(szdriverattr)  'the varptr function returns the address of a variable.
         decr ilen2                    'so ptrbyte holds the address of the 256 byte long asciiz
         for i=0 to ilen2              'string buffer.  the ilen2 parameter holds the one based
           if @ptrbyte[i]=0 then       'count of characters returned.  i decremented it because
              @ptrbyte[i]=44           'in using a base pointer you are working with zero based
           end if                      'offsets from a starting point.  pointer subscript notation
         next i                        'is used to move through buffer substituting ','s for 0s.
         @ptrbyte[ilen2]=0             'finally, insert null byte at trailing comma.
         redim strarr(parsecount(szdriverattr)-1) 'for above string we need elements 0 through 6.
         parse szdriverattr,strarr()              'parse will extract each attribute/value pair
         for i=0 to ubound(strarr,1)              'and load strarr() starting at element zero
           display(strarr(i),fn)                  'and incrementing until all elements extracted.
         next i                                   'the ubound(strarr) is parsecount-1.
         display(chr$(13,10) & chr$(13),fn)
         call sqlfreehandle(%sql_handle_env,henvr)
      else
         display("odbc function call failure.  couldn't allocate environment handle!",fn)
      end if
      #if %def(%pb_cc32)
          waitkey$
      #else
          close #fn
          szcurdir=curdir$
          call shellexecute(0,"open",sztextfile,byval 0,szcurdir,%sw_showdefault)
      #endif
      '
      pbmain=0
    end function
    at this point i don't believe i need to provide any further examples as we've come full circle.
    the next version of the program is the first program at the top of this post where sqldrivers() is
    called repeatedly inside a while loop with the exit condition being when there is no more data to
    retrieve marked by sqldrivers() returning %sql_no_data.

    this program shows a particularly involved api function, and if you were able to follow along
    and understand it, you will easily be able to master the api functions required to create a graphical
    interfaces in windows. probably the hardest issues for programmers whose programming background is
    limited to basic are the issues relating to character string buffers, pointers to character string
    buffers, and null terminated asciiz strings. some insight into these issues can be obtained by
    carefully reading the powerbasic documentation on all the string data types and pointers.



    [this message has been edited by fred harris (edited january 08, 2007).]
    Fred
    "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"
Working...
X