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:
- A COM Byte array in a Dynamic instance cannot be passed as a parameter unless it's explicitly cast to object
- 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>
Other Posts you might also like