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

Accessing a SafeArray Result from a COM Call in C#


:P
On this page:

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:

 byteArray[9]

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>

Posted in COM  CSharp  

The Voices of Reason


 

Luc
August 26, 2008

# re: Accessing a SafeArray Result from a COM Call in C#?

Seriously! COM Interop is such a hassle! It's nice that we have to fall back on, but the work that we must go through to support that backward compatibility is rather frustrating. Good luck with this one! :)

Christof Wollenhaupt
August 26, 2008

# re: Accessing a SafeArray Result from a COM Call in C#?

Non-zero based array, how funny... What about something along these lines:

Byte[] content = new byte[((Array)zipContent).Length];
((Array)zipContent).CopyTo(content, 0);

Kevin Dente
August 26, 2008

# re: Accessing a SafeArray Result from a COM Call in C#?

If I recall, SAFEARRAYs are returned as instances of an Array object, and you have to use Array.GetValue/SetValue to interact with it. If the API is being generated by tlbimp, you can use the /sysarray flag to automatically convert to a managed array, but I can't tell if you're using tlbimp here (doesn't look like it).

bgr
December 21, 2008

# re: Accessing a SafeArray Result from a COM Call in C#

Thank you, I was about to give up but I finally found the answer, here ;)

JB
May 29, 2009

# re: Accessing a SafeArray Result from a COM Call in C#

You could create a new byte array, as you are doing now...

Or you could just do this:

byte[] content = (byte[])(Array)zipContent;

Surya
December 09, 2010

# re: Accessing a SafeArray Result from a COM Call in C#

Hello,

I am having trouble with getting the array of class object (classobj[] data) back from the COM to managed.

But same works when I send the array of managed object (classobj[] data) to COM.

When I receive the array data, array length is 1, which is good, but the data inside is not the same as I set in COM world.

Thanks for the help


Surya

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