Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

COM ByteArray and Dynamic type issues in .NET


:P
On this page:

In .NET 4.0 the dynamic type has made it A LOT easier to work with COM interop components.  I'm still working quite a bit with customers that need to work with FoxPro COM objects in .NET and there are some extra fun issues to deal with when working with COM SafeArrays (byte arrays) returned from a COM client. Currently I'm working with a customer who needs to work with Web Services that publish and capture a lot of binary data. A couple of interesting things came up when returning byte arrays over COM to .NET via a dynamic type.

I've written about COM binary arrays before in a previous post. The issue is that when you make a COM call to retrieve a binary result from a COM object the result gets returned as byte[*] type rather than byte[].

To demonstrate imagine you have a simple COM client that returns a binary result as a SafeArray of bytes. The following dumbed down example is a FoxPro method in  COM published component where an object is returned that contains a ByteResult property that holds binary data:

************************************************************************
*  SendBinaryFile
****************************************
FUNCTION SendBinaryFile(lcPath) 
LOCAL lvBinary, lcBinary, loResult as ServiceResult

TRY
    *** Grab the file from disk into a string
    lcBinary = FILETOSTR(lcPath)

    *** Turn the string to binary - over COM this turns into a SAFEARRAY
    lvBinary = CREATEBINARY(lcBinary)  && OR CAST(lcBinary as Q)

    *** Return value is standard Service Response object
    loResult = CREATEOBJECT("ServiceResult")

    *** Attach the binary response to the ServiceResult
    loResult.ByteResult = lvBinary
CATCH TO loException
    loResult = this.ErrorToServiceResult(loException,20)
ENDTRY

RETURN loResult
ENDFUNC

The interesting part in relation to this post is the binary result which in this case is the content of a file. Internally FoxPro treats binary values as strings since it's stuck with ANSI codepages, so string can easily contain binary data. However FoxPro cannot return that string as a binary value over COM. In order to return a binary value the value needs to be explicitly converted using CREATEBINARY() or CAST(val as Q) which turns it into a SAFEARRAY when passed back over COM. This binary value is then assigned to a ServiceResult object that returns actual result value(s) back to the .NET Web Service which in turn publishes these values through the Service interface.

In .NET the sample Web Service method that handles publishing of this data looks like this. Note that this code as written does not work although it probably looks like it should. The code that doesn't work is shown in bold (two separate issues):

[WebMethod]
public ServiceResult SendBinaryFile()
{
    var serviceResult = new ServiceResult();

    try
    {
        // Returns a COM ServiceResult - result.ByteResult is a COM SafeArray
        dynamic result = ComProxy.SendBinaryFile(Server.MapPath("~/images/sailbig.jpg"));

        byte[] byteResult = ComArrayToByteArray(result.ByteResult);        

        //serviceResult.FromServiceResult(result);
    }
    catch (Exception ex)
    {
        // Assign the error infor from Exception to ServiceResult
        serviceResult.FromException(ex);
    }
    return serviceResult;
}

byte[] ComArrayToByteArray(object comArray)
{
    byte[] content = comArray as byte[];
    return content;
}

Initially this code fails on the actual ComArrayToByteArray() method call, while actually calling the method with the dynamic COM byte array object as a parameter. The code fails with:

Unable to cast object of type 'System.Byte[*]' to type 'System.Byte[]'.

What's interesting - and different than my previous post and solution - is that the call here fails simply calling the method with a dynamic value that is a COM byte array. The error fires before any code in the method fires. The target method - ComArrayToByteArray() accepts an object parameter and yet it fails!!! It's not the code in the method that fails, it's the actual method call. Completely unexpected!

How to fix this? You have to explicitly cast the result.ByteResult - the COM byte array - to type object:       

byte[] byteResult = ComArrayToByteArray(result.ByteResult as object);    

and then the call to the method call works. Unfortunately there's another problem and the method call still fails now on:

byte[] content = comArray as byte[];

The issue here is that the comArray parameter value passed in as object from the dynamic byte array, is actually of type byte[*] which cannot just be cast to byte[]. A little more work is required to convert this value to a byte[] araray. The ComArrayToByteArray() method needs to be rewritten like this:

byte[] ComArrayToByteArray(object comArray)
{
    Array ct = (Array) comArray;
    byte[] content = new byte[ct.Length];
    ct.CopyTo(content, 0);
    return content;
}

and now finally the code works as expected. The complete code that works in converting the COM byte array from a dynamic into byte[] looks like this:

[WebMethod]
public ServiceResult SendBinaryFile()
{
    var serviceResult = new ServiceResult();

    try
    {
        // Returns a COM ServiceResult - result.ByteResult is a COM SafeArray
        dynamic result = ComProxy.SendBinaryFile(Server.MapPath("~/images/sailbig.jpg"));

        byte[] byteResult = ComArrayToByteArray(result.ByteResult as object);        

        //serviceResult.FromServiceResult(result);
    }
    catch (Exception ex)
    {
        // Assign the error infor from Exception to ServiceResult
        serviceResult.FromException(ex);
    }
    return serviceResult;
}

byte[] ComArrayToByteArray(object comArray)
{
    Array ct = (Array)comArray;
    byte[] content = new byte[ct.Length];
    ct.CopyTo(content, 0);
    return content;
}

Summary

As you might imagine this can bite you in many unsuspecting ways especially when using dynamic types since these are going to be runtime type conversion errors. Just be aware that when dealing with byte arrays returned over COM and especially when returning them into Dynamic types there are additional things you need to check for.

In summary there are two issues here:

  1. A COM Byte array in a Dynamic instance cannot be passed as a parameter unless it's explicitly cast to object
  2. A COM Byte array cannot be cast directly to byte[] but requires conversion to an array and copying of each byte

It's not a super complicated workaround, but rather unexpected behavior. The difference between byte[*] and byte[] seems inconsequential until you end up having to cast between the two. Not sure why you'd ever want to have a type of byte[*] - since it appears you can't do anything with it other than copy the explicit bytes around. <shrug>

Posted in .NET  COM  FoxPro  

The Voices of Reason


 

Dominic Zukiewicz
July 18, 2011

# re: COM ByteArray and Dynamic type issues in .NET

Hi Rick,

System.Byte[*] are non-zero arrays. C# does support them, but you have to create them through Array.CreateInstance(<type>,<length>,<non-zero-starting-index>);.

You can then use Array.Copy, so long as you know the bounds to copy in between.

static void Main(string[] args)
{
            // Length = 10, Starting-index = 1
            Array offCentre = Array.CreateInstance(typeof(byte), new[] { 10 }, new[] { 1 });
 
            // Get dimensions
            int length = offCentre.Length;
            int lowerBounds = offCentre.GetLowerBound(0);
            int upperBounds = offCentre.GetUpperBound(0);
 
            Console.WriteLine("offCentre.GetType() = {0}, Lower = {1}, Upper = {2}",offCentre.GetType(),lowerBounds,upperBounds);
 
            // Create a normal array via the same means
            Array newArray = Array.CreateInstance(typeof(byte), length);
 
            // Copy from the low bounds to the normal array
            Array.Copy(offCentre, lowerBounds, newArray, 0,length);
 
            for (int index = 0; index < length; index++)
            {
                Console.WriteLine(
                    "{0}: offCentre[{0}] = {1}, newArray[{0}] = {2}",
                    index,
                    offCentre.GetValue(index),  // <-- Won't work as 'index' is 0
                    newArray.GetValue(index));
            }
 
            Console.Read();
        }


Hope this helps you sort the copying problemo

Rick Strahl
July 18, 2011

# re: COM ByteArray and Dynamic type issues in .NET

Thanks @Dominic. It's all working but one would hope that the RCW and dynamic types would know about this and internally perform that conversion for you. Once you know the issue is easy to resolve, but finding info on byte[*] at the time didn't turn up anything to useful - very unintuitive to discover the solution to this.

Dominic Zukiewicz
July 20, 2011

# re: COM ByteArray and Dynamic type issues in .NET

@Rick,

I only recognised your issue, as I've read a book called CLR via C# by Jeffrey Richter and he explains this feature in the CLR and in VB (but not natively in C#). TBH, even I struggled to find it on Google and I knew what I was looking for! :-) Keep up the good work.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024