I too would like to share Shannon
's sentiment and extended it to all the other contributors.
Thanks all,
Rob
-----Original Message-----
From: rpg400-l-bounces@xxxxxxxxxxxx [mailto:rpg400-l-bounces@xxxxxxxxxxxx]
On Behalf Of Shannon ODonnell
Sent: November 29, 2007 10:46 PM
To: 'RPG programming on the AS400 / iSeries'
Subject: RE: Send_File API versus Send API over a Socket
Thanks Scott! I got that prototype from Cozzi's website, but it's possible
that I coded it as "I" where he had it as "U". I could go back and look but
I'll assume he had it correct and I miscoded it.
I had forgotten to tell you that I was using the "rb" options in the flags
parm on the _C_IFS_Fopen call, so that it does read it as binary already.
This was a very detailed response and really helped me to understand this
topic. The documentation on some of the APIs sucks so bad that it's actively
useless. That is, it seems to go out of its way to be cryptic and
misleading, at least to traditional iSeries programmers like me.
I will never understand how you have managed to learn all of the things you
are so expert in. When do you have time to sleep? There seems to be no
question about any iSeries programming that you don't know the answer to
already!
And you're always helping everyone on these lists and on the other forums as
well.
So, as this year winds down, I want to make sure that I take the opportunity
to say thank you to you for all the times your knowledge has helped me, even
indirectly when you have answered someone else's question(s).
Thanks again!
Shannon O'Donnell
-----Original Message-----
From: rpg400-l-bounces@xxxxxxxxxxxx [mailto:rpg400-l-bounces@xxxxxxxxxxxx]
On Behalf Of Scott Klement
Sent: Thursday, November 29, 2007 7:08 PM
To: RPG programming on the AS400 / iSeries
Subject: Re: Send_File API versus Send API over a Socket
Hi Shannon,
take a look at this and give me your
opinion on whether or not I coded this correctly for this bit of code
(please):
The way you're coding it will run the risk of corrupting the data at the
end of the file because it doesn't keep track of the length of the data
you read.
When you read the data by calling fread(), how much data gets loaded
into your 'outBuffer' variable? You have the variable defined to be
32767 -- which is fine, but what if there's less than 32767 bytes left
to read in th file?
When you work with binary data, it's important to track the length. You
can't trim blanks (which are x'40') or look for x'00' to determine the
length since these are valid values in binary data -- and therefore they
don't mean anything. They might just be part of the data you read, and
they might not...
The only reliable way to read/write binary data is by keeping track of
the length read from disk, and using that same length to write to the
socket.
With that in mind, here's my thoughts on your code:
a) In C, the length variables that your API returns are all UNSIGNED
fields (so they'd be data type U, not I as you have them coded). So
your prototype SHOULD look like this: (Notice U instead of I)
D ifsReadByte PR 10u 0 ExtProc('_C_IFS_fread')
D inBuffer * Value
D inUnitSize 10u 0 Value
D inBufLen 10u 0 Value
D inFilePtr * Value
b) Your dow loop is discarding the length that fread() returns. It
needs to be changed to save that length. This means that ifsReadByte()
can't be coded on the DOW expression, it needs to be separate. You'll
have to either do the reading at the top of the loop, or you'll have to
code a "priming read". (Unless, of course, you want to wrap the whole
thing into a subprocedure...)
For example:
C dou len < 1
C eval len = ifsReadByte( %addr(outBuffer)
C : 1
C : %size(outBuffer)
C : zp )
C if len >= 1
c callp(e) WriteDataToSocket( sockDescriptor
c : outBuffer
c : len )
c endif
C Enddo
c) As I described above, the length is very important when working with
binary data. You'll see in the code above that I pass the 'len'
variable to the WriteDataToSocket() routine... that means that routine
will have to be modified to accept this extra parameter, and use it for
the value it passes to the send() (or write() or whatever you've coded) API.
The parameter here that confuses me (mostly because I cannot find enough
good documentation on it that I can understand) is the inUnitSize. I
thought I understood what that meant, (i.e., Size in bytes of each element
to be read.), but it does not seem to make a difference what size I make
that, as long as it's not 1. So I'm thinking I don't really understand how
the inUnitSize and inBuflen work together.
It's for working with arrays of fixed-length data. Let's say you had
an array like the following one:
D MyArray s 31p 4 dim(26)
Keep in mind, of course, that these routines are for C, and the C
language exists everywhere, not just i5/OS. Most platforms don't have a
built in database, and they save everything they do in stream files
(like the ones we normally associate with the IFS).
To save the contents of the MyArray array to disk, you'd code something
like this:
fwrite( %addr(MyArray): 16: 26: zp);
The 16 is because each packed number in the array is 16 bytes long (a
31p 4 field takes up 16 bytes of memory). The 26 is because there are
26 elements. The fwrite() API would return 26 to tell you that all 26
array elements were written.
Of course, that could would be better written if it used %size() and
%elem() rather than hard-coding the 16 and 26, but I thought I'd code
the actual numbers to make it a little clearer.
fwrite( %addr(MyArray): %size(MyArray): %elem(MyArray): zp);
(Note: when you use %size() on an array, and don't add *ALL, it only
gives you the size of one element -- 16 in this example.)
When a program wanted to read back from disk, it'd do this:
count = fread( %addr(MyArray): %size(MyArray): %elem(MyArray): zp);
Now count = the number of array elements that were loaded from disk (26
if the entire array was loaded.).
If I were working with an array of data structures -- or multiple
occurrence data structure -- then I could use fread() and fwrite() to
read and write arrays of records -- making the support very similar to
working with a program described, unkeyed, database file... and the
count returned from fread() would be the number of records read... a
useful tool on those platforms that don't have the built-in database.
Having the separate parameters for "size of an element" and "number of
elements" is really just a convenience thing for the programmer. Under
the covers, fread and fwrite really just multiply the two fields
together to get a total number of bytes, and then they read or write
that many bytes.
So in your case where you're just reading a buffer full of bytes, you
want to set the size of each element (the inUnitSize parm in your
prototype) to be 1, because the units you're reading are bytes -- and
each byte is (duh) one byte long.
And you want to set the 'number of elements' parameter (inBufSize in
your prototype) to be the total number of bytes you want to read or
write on this call to fread/fwrite.
That's why, if you look at my re-design of your code (near the start of
this message) you'll see I wrote it like this:
C eval len = ifsReadByte( %addr(outBuffer)
C : 1
C : %size(outBuffer)
C : zp )
The size of each element is 1, the number of elements is the %size() of
your output buffer. So when it multiples %size(outBuffer) by 1, it'll
come up with %size(outBuffer) and read that many bytes.
The API returns the number of elements (not the number of bytes) read.
But, since (in this example) each element is one byte long, the number
of bytes and the number of elements are the same thing. (In my array
examples, above, they were not, however...)
If you reversed the parameters and coded it this way:
C eval len = ifsReadByte( %addr(outBuffer)
C : %size(outBuffer)
C : 1
C : zp )
This (incorrect, IMHO) version of the code tells the API that I only
want to read one element, and it's 32767 bytes long. Again, the API
just multiplies the fields, so the total bytes it reads would be
correct. The problem is, the API will return either 0 or 1, since it
returns the number of elements, and I only told it to read one element!
This is not very useful unless you're working with data that's always
fixed-length. With this code I can't tell how many bytes outBuffer is.
But coding it the other way (setting the size of each element to 1, and
setting the number of elements to the numebr of bytes) the return value
will be the number of actual bytes read.
C eval len = ifsReadByte( %addr(outBuffer)
C : 1
C : %size(outBuffer)
C : zp )
Hopefully that clarifies what those two fields are for, and what you'll
want to use.
I'm not translating to EBCDIC and then back to ASCII or doing any
translation at all, by the way.
Again -- let me emphasize that the fopen() API will AUTOMATICALLY do
this translation unless you tell it not to. Code the 'b' flag on
fopen() to tell it you do not wish to have translation done.
As an Amazon Associate we earn from qualifying purchases.