Working with System.IO.Stream Read Method - ArgumentNullExce

Posted by saquib on 29-Mar-2012 10:07

Hello,

In the processing of translating a C# code example to its OpenEdge equivalent, but hitting a brick wall when working with System.IO.Stream. Take the example below, the aim is to read from a input stream and re-write the output (stripped out some of the code to keep it simple)

          DEFINE VARIABLE file AS CLASS System.IO.FileStream.

          DEFINE VARIABLE fstream as CLASS System.IO.Stream.

          DEFINE VARIABLE len AS INT NO-UNDO.
           file = New System.IO.FileStream("c:\\temp\\" + attachmentFile, System.IO.FileMode:CreateNew).
                 
           fstream = resource:GetInputStream(attachmentUrl). 
           len = fstream:Read(output fbuffer, 0, fbuffer:length).
The final line is throwing the ArgumentNullException, which according to the MSDN  'http://msdn.microsoft.com/en-us/library/system.io.stream.read.aspx indicates the first argument is null.
I've tried defining fbuffer in 2 different ways with both syntax checking fine, but both give the same error at run-time:
  DEFINE fbuffer AS INT EXTENT 2048.
* obviously replacing fbuffer:length with extent(fbuffer)
and
          DEFINE VARIABLE fbuffer AS "System.Byte[]" NO-UNDO.
          fbuffer = NEW "System.Byte[]" (2048).
I can use the ReadByte() method and that does allow me to read the contents of the input stream, but it is very slow.
Any ideas on what the issue may be?
Thanks.

All Replies

Posted by Matt Baker on 29-Mar-2012 10:56

It looks like it might be a bug where the compiler misinterprets the Read() method byte[] parameter as needing output, but in fact it needs input.  You will need to contact tech support about it.

Following is a simplified version of your code.

define variable file    as class
System.IO.FileStream.


define variable fstream as class System.IO.Stream.


define variable len     as int   no-undo.


file = new System.IO.FileStream(this-procedure:file-name, System.IO.FileMode:Open).


         


define variable fbuffer as "System.Byte[]" no-undo.




fbuffer = new "System.Byte[]" (2048).       




len = file:Read(output fbuffer, 1, fbuffer:length - 1). 

Posted by Laura Stern on 29-Mar-2012 10:59

Based on the documentation, clearly the first parameter of Read() is not an output parameter.  Since you specified OUTPUT, that is why it is saying it's null - because we don't actually pass any value for an OUTPUT parameter;  The value is expected to come back, not be passed in.  But if the compiler complains if you don't specify OUTPUT, then it seems like there is a bug here.  I would contact tech-support and report this.

I have no other suggestion about how to do this off the top of my head.  Though I know there are lots of IO objects, like a buffered reader, or the like.  I would look around for another object to use.

Posted by saquib on 29-Mar-2012 12:59

Thanks for the replies. Replying from my mobile so don't have all the information to hand ,but I did try the first parameter without specifying output and the compiler did not give an error but the same run time error was generated.

Clearly the first parameter is an output as that is what holds the returned data ?

Posted by Laura Stern on 29-Mar-2012 13:17

Now that's odd - that you'd get the ArgumentNullException, when the argument is not null and it's INPUT.  I can just reiterate that a bug should be logged.

But no - it is not true that "the first parameter must be an output as that is what holds the returned data".  The argument is an array of bytes.  So the returned data is stored in the bytes.  The doc states very clearly that the routine fills up the bytes in the specified array beginning at the start position, for length bytes.  To be a true output parameter, if the parameter is an object, the routine would return a new object.  It's all in the semantics!

Posted by Matt Baker on 29-Mar-2012 13:17

The first parameter is not supposed to be an output parameter.

http://msdn.microsoft.com/en-us/library/system.io.filestream.aspx

Its an input parameter of a (normally zero'd) byte array, that the read method will fill.  But the ABL compiler thinks its supposed to be output which is wrong, so it ends up not passing in the byte array, which results in the read method throwing an exception stating it received a null buffer.

Posted by saquib on 30-Mar-2012 04:44

Will log a call. I do have an alternative solution, using the ReadByte method, but that is slooow (20s to read/write a 700kb file). I've tried to minimise the writes by buffering into an array (extent 2048) first, but still too slow;

          repeat:

              do i = 1 to 2048:

                  dbyte = fstream:ReadByte().

                  if dbyte = -1 then

                     leave.

                  fbuffer1[i] = dbyte.

              end.

              file:Write(fbuffer1, 0, i - 1).

              if dbyte = -1 then

                  leave.                 

          end.

There is another method similar to Read called BeginRead;

METHOD PUBLIC System.IAsyncResult BeginRead (array AS CLASS "System.Byte[]", offset AS integer,  numBytes AS integer, userCallback AS CLASS System.AsyncCallback,  stateObject AS CLASS System.Object)

Not tried implementing that yet (not sure how to set-up the callback function), but interestingly, the first parameter on this method is also an array of System.Byte, but it correctly recognised as not being an output parameter.

Posted by Matt Baker on 30-Mar-2012 09:59

File streams by themselves are unbuffered.  This usually means a call to the OS each time it needs to read a single byte.  Make sure you are using a buffered stream wrapped around the file stream, that way bytes are read in chunks out of the file, and the single byte read does so from memory:

http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx

define variable file    as class System.IO.FileStream.
define variable fstream as class System.IO.Stream.
define variable len     as int   no-undo.
file = new System.IOBufferedStream(new System.IO.FileStream(this-procedure:file-name, System.IO.FileMode:Open), ).
        
....

Posted by saquib on 02-Apr-2012 04:06

Thanks Matthew.Unfortunately, the bufferstream has the same issue (the .NET compiler incorrecly recognising the first parameter on the read method as an output), and gives the same run-time error.

I've actually worked around the issue, I'm working with a control to read emails from a Microsoft Exchange server, rather than streaming the attachments from the server, I'm now downloading the msg files locally and then extracting the attachments. Much faster.

Thanks for all your help.

This thread is closed