I'm calling a COM object from managed code that's returning a binary response, which is returned as a SafeArray of bytes from the COM server. My managed code uses Reflection to retrieve the result from the COM Server like this:
// Results in a binary (SafeArray) response from COM object
// .ToString shows: System.Byte[*] as the type
object zipContent = wwUtils.CallMethodCom(this.HelpBuilder,
"CreateHelpZip", "DOTNETASSEMBLY", "TestProject",
"MSDN",
@"C:\projects2008\Westwind.Tools\bin\Debug\Westwind.Tools.dll", 50);
// *** This fails
byte[] content = (byte[])zipContent;
CallMethodCom simply is a wrapper around Type.Invoke() so it's basic Reflection call. The method call works fine and it appears to return the expected result at least from what I can see in the debugger. But in code
What's interesting is that when I step through this code the code comes back properly. I get an object that according to the debugger contains a valid byte[] array:
But the code that casts the result fails. I get an error that declares: "Unable to cast object of type 'System.Byte[*]' to type 'System.Byte[]".
Haeh? System.Byte[*]? What the heck is that? A 'fixed width' byte array? What's really confusing here is that the debugger shows the object properly as byte[]. I can even cast to byte[] in the Immediate Window and get at the byte[] properties like Length. So it's working but somehow the generated C# runtime code fails to cast the byte array.
[Updated based on Comments from Christof and Kevin]
It turns out there is a way to reference the SafeArray in .NET which is as a non typed, one-based array. You can interact with this array only using GetValue(),SetValue() or by copying it out using CopyTo(), which effectively means the only way to retrieve the data is to copy it into a byte array or else write it out one byte at a time.
The following code stores the SafeArray into an array cast, then copies the array contents into a byte array:
// Results in a binary (SafeArray) response from COM object
object zipContent = wwUtils.CallMethodCom(this.HelpBuilder,
"CreateHelpZip",
this.Mode,
this.ProjectTitle, "MSDN",
dlPath + sourceFile, 50);
// *** Must convert to Array first then copy out - note 1 based
Array ct = (Array)zipContent;
Byte[] content = new byte[ct.Length];
ct.CopyTo(content, 0);
Response.ContentType = "application/x-zip-compressed";
Response.BinaryWrite(content);
Thankfully this works without any additional casts. Another option would be to loop through the array and retrieve each element and BinaryWrite() that out 1 byte at a time, which would help avoid the double memory hit of a copy. In the above code this might actually be useful because the output can be about a megs worth of data.
Ultimately the cleaner solution would be to have the COM server's code output directly to a file which can then be streamed directly by IIS with TransmitFile, but for the moment I don't want to muck with the COM server's interface.
Anyway - thanks to Christof and Kevin who pointed me in the right direction. Ah yes, this WebLog does have it's rewards at times. <s>
Other Posts you might also like