Problem Sending VB6 Byte Array to C#...help?

I

intrepid_dw

Hello, all.

I've created a C# dll that contains, among other things, two functions
dealing with byte arrays. The first is a function that returns a byte
array, and the other is intended to receive a byte array as one of its
parameters. The project is marked for COM interop, and that all
proceeds normally.

When I reference the type library in the VB6 project, and write the
code to call the function that returns the byte array, it works
perfectly. However, when I write the code to call the method that
expects the byte arrary as a parameter, VB informs me that "Function is
marked restricted, or uses a type not supported by Automation." The
Object Browser shows the library AND the method with its (correct)
parameter list.

The latter portion of the warning didn't ring entirely true to me,
because I had returned a byte array with no problem. It occurred to me
there might be a problem on the outbound side.

As a test, I built a quick-and-dirty VB6 COM DLL with a method that
expected a byte array, and the OLEView utility for that DLL showed the
TLB for that method showed this signature:

VB6 dummy COM DLL method:
HRESULT TestMethod( [in] SAFEARRAY(unsigned char)* parm,
[out, retval] BSTR* pRetVal);

However, when I looked at the IDL for the Interop assembly of my C#
code, the Byte array parameter signature was as follows:

C# class method w/System.Byte[] as parameter
HRESULT MyMethod( [in] SAFEARRAY(unsigned char) FileContent,
[in] BSTR FileComment,
[out, retval] BSTR* pRetVal);

Note the difference in the signatures for the first parameter; the
first is [in] SAFEARRAY(unsigned char)*, but the latter is only [in]
SAFEARRAY(unsigned char). Why the difference?? Each is expecting the
same thing.

My initial thought was that this, perhaps, was a bug in the IDL/TLB
generation code from C#, but that seemed unlikely (?). Can anyone else
offer any suggestions or help?

Ultimately, I really just need to pass a byte array from VB6 to C#, so
I figure someone is bound to have done that before. If anyone has any
suggestions, I'd be appreciative.

Thanks,
David

ps Please reply to group; email herein is long since dead.
 
K

Kevin Spencer

Note the difference in the signatures for the first parameter; the
first is [in] SAFEARRAY(unsigned char)*, but the latter is only [in]
SAFEARRAY(unsigned char). Why the difference?? Each is expecting the
same thing.

Not exactly the same thing. The one that works is looking for a pointer to a
byte array. The second is looking for a byte array.

It sounds like you need to build a COM interop wrapper for the VB6 DLL.

--
HTH,

Kevin Spencer
Microsoft MVP
..Net Developer
Ambiguity has a certain quality to it.

Hello, all.

I've created a C# dll that contains, among other things, two functions
dealing with byte arrays. The first is a function that returns a byte
array, and the other is intended to receive a byte array as one of its
parameters. The project is marked for COM interop, and that all
proceeds normally.

When I reference the type library in the VB6 project, and write the
code to call the function that returns the byte array, it works
perfectly. However, when I write the code to call the method that
expects the byte arrary as a parameter, VB informs me that "Function is
marked restricted, or uses a type not supported by Automation." The
Object Browser shows the library AND the method with its (correct)
parameter list.

The latter portion of the warning didn't ring entirely true to me,
because I had returned a byte array with no problem. It occurred to me
there might be a problem on the outbound side.

As a test, I built a quick-and-dirty VB6 COM DLL with a method that
expected a byte array, and the OLEView utility for that DLL showed the
TLB for that method showed this signature:

VB6 dummy COM DLL method:
HRESULT TestMethod( [in] SAFEARRAY(unsigned char)* parm,
[out, retval] BSTR* pRetVal);

However, when I looked at the IDL for the Interop assembly of my C#
code, the Byte array parameter signature was as follows:

C# class method w/System.Byte[] as parameter
HRESULT MyMethod( [in] SAFEARRAY(unsigned char) FileContent,
[in] BSTR FileComment,
[out, retval] BSTR* pRetVal);

Note the difference in the signatures for the first parameter; the
first is [in] SAFEARRAY(unsigned char)*, but the latter is only [in]
SAFEARRAY(unsigned char). Why the difference?? Each is expecting the
same thing.

My initial thought was that this, perhaps, was a bug in the IDL/TLB
generation code from C#, but that seemed unlikely (?). Can anyone else
offer any suggestions or help?

Ultimately, I really just need to pass a byte array from VB6 to C#, so
I figure someone is bound to have done that before. If anyone has any
suggestions, I'd be appreciative.

Thanks,
David

ps Please reply to group; email herein is long since dead.
 
I

intrepid_dw

Kevin:

Thanks for your help.

I reread my original post, and realized it was a bit muddled. My
apologies for not making it more clear. There are three pieces of code
involved; A C# Interop DLL, a "client" VB6 application that will use
the C# interop assembly, and then a throwaway "test" VB6 COM DLL
created only to test how the type library
records the parameters in the method signatures.

My expectation was that the Interop assembly would create a type
library expecting a **pointer** to a SAFEARRAY of unsigned characters,
but it didn't; it was just the SAFEARRAY. I didn't think that seemed
right, so I built a "test" VB6 COM DLL to test what IT would generate
for a byte array in the type library method signatures.

See the declarations below:

// C# decl in interop assembly, no pointer...
// TLB generates [in] SAFEARRAY(unsigned char) FileContent
public string MyMethod(System.Byte[] FileContent,
String FileComment)


// VB6 "dummy" declaration to compare type library signatures
// TLB has *pointer* reference, eg
// this one generates [in] SAFEARRAY(unsigned char)* parm
public function TestMethod(parm() as byte) as String

Sure enough, my prediction was right; the VB6 COM DLL type library
wanted a *pointer* to a SAFEARRAY of unsigned chars, and hence my
question. If two different methods in two different DLL's are both
exposing a COM interface, and each is expecting a byte array, why
wouldn't the type library for *both* expect a pointer to a SAFEARRAY of
unsigned chars? It would seem to me the type library signatures should
match, and they don't.

Is that explanation any better? I'm sure I'm overlooking something
obvious, just not sure what it is.

Thanks again for your help,
David


Kevin said:
Note the difference in the signatures for the first parameter; the
first is [in] SAFEARRAY(unsigned char)*, but the latter is only [in]
SAFEARRAY(unsigned char). Why the difference?? Each is expecting the
same thing.

Not exactly the same thing. The one that works is looking for a pointer to a
byte array. The second is looking for a byte array.

It sounds like you need to build a COM interop wrapper for the VB6 DLL.

--
HTH,

Kevin Spencer
Microsoft MVP
.Net Developer
Ambiguity has a certain quality to it.

Hello, all.

I've created a C# dll that contains, among other things, two functions
dealing with byte arrays. The first is a function that returns a byte
array, and the other is intended to receive a byte array as one of its
parameters. The project is marked for COM interop, and that all
proceeds normally.

When I reference the type library in the VB6 project, and write the
code to call the function that returns the byte array, it works
perfectly. However, when I write the code to call the method that
expects the byte arrary as a parameter, VB informs me that "Function is
marked restricted, or uses a type not supported by Automation." The
Object Browser shows the library AND the method with its (correct)
parameter list.

The latter portion of the warning didn't ring entirely true to me,
because I had returned a byte array with no problem. It occurred to me
there might be a problem on the outbound side.

As a test, I built a quick-and-dirty VB6 COM DLL with a method that
expected a byte array, and the OLEView utility for that DLL showed the
TLB for that method showed this signature:

VB6 dummy COM DLL method:
HRESULT TestMethod( [in] SAFEARRAY(unsigned char)* parm,
[out, retval] BSTR* pRetVal);

However, when I looked at the IDL for the Interop assembly of my C#
code, the Byte array parameter signature was as follows:

C# class method w/System.Byte[] as parameter
HRESULT MyMethod( [in] SAFEARRAY(unsigned char) FileContent,
[in] BSTR FileComment,
[out, retval] BSTR* pRetVal);

Note the difference in the signatures for the first parameter; the
first is [in] SAFEARRAY(unsigned char)*, but the latter is only [in]
SAFEARRAY(unsigned char). Why the difference?? Each is expecting the
same thing.

My initial thought was that this, perhaps, was a bug in the IDL/TLB
generation code from C#, but that seemed unlikely (?). Can anyone else
offer any suggestions or help?

Ultimately, I really just need to pass a byte array from VB6 to C#, so
I figure someone is bound to have done that before. If anyone has any
suggestions, I'd be appreciative.

Thanks,
David

ps Please reply to group; email herein is long since dead.
 
K

Kevin Spencer

Well, David, I'm still a bit confused. You have a VB6 COM DLL, and an
Interop .Net class that interoperates with it, and which was generated by a
tool. So far so good? Now, here it starts to get fuzzy. You are now working
with the Interop assembly, and not directly with the COM DLL, correct? So,
you call a method in the Interop DLL that returns a byte array, and you get
one back. Am I still on the right track? But you call a method in the
Interop that is supposed to take byte array as a parameter, and it fails
because the Interop DLL is expecting a pointer to a byte array? Now, this is
where it starts to get really fuzzy. First, I can't imagine a managed
Interop DLL that would be expecting a pointer. Still, I suppose it's
possible. Second, you say you created ANOTHER VB6 COM DLL and compared its
type library reference to the type library reference in the Interop, and
they didn't match? Well, why should they? First, the second COM DLL is not
the one the Interop is working with. You would have to create another
Interop for that DLL and compare IT to the type library in the second COM
DLL.

So, at this point, being thoroughly fuddled, I can mention only that the
tools for creating COM Interop assemblies don't always work correctly
straight out of the box, and you may need to tweak the Interop assembly. But
as confused as I am, I can only guess.

--
HTH,

Kevin Spencer
Microsoft MVP
..Net Developer
Ambiguity has a certain quality to it.

Kevin:

Thanks for your help.

I reread my original post, and realized it was a bit muddled. My
apologies for not making it more clear. There are three pieces of code
involved; A C# Interop DLL, a "client" VB6 application that will use
the C# interop assembly, and then a throwaway "test" VB6 COM DLL
created only to test how the type library
records the parameters in the method signatures.

My expectation was that the Interop assembly would create a type
library expecting a **pointer** to a SAFEARRAY of unsigned characters,
but it didn't; it was just the SAFEARRAY. I didn't think that seemed
right, so I built a "test" VB6 COM DLL to test what IT would generate
for a byte array in the type library method signatures.

See the declarations below:

// C# decl in interop assembly, no pointer...
// TLB generates [in] SAFEARRAY(unsigned char) FileContent
public string MyMethod(System.Byte[] FileContent,
String FileComment)


// VB6 "dummy" declaration to compare type library signatures
// TLB has *pointer* reference, eg
// this one generates [in] SAFEARRAY(unsigned char)* parm
public function TestMethod(parm() as byte) as String

Sure enough, my prediction was right; the VB6 COM DLL type library
wanted a *pointer* to a SAFEARRAY of unsigned chars, and hence my
question. If two different methods in two different DLL's are both
exposing a COM interface, and each is expecting a byte array, why
wouldn't the type library for *both* expect a pointer to a SAFEARRAY of
unsigned chars? It would seem to me the type library signatures should
match, and they don't.

Is that explanation any better? I'm sure I'm overlooking something
obvious, just not sure what it is.

Thanks again for your help,
David


Kevin said:
Note the difference in the signatures for the first parameter; the
first is [in] SAFEARRAY(unsigned char)*, but the latter is only [in]
SAFEARRAY(unsigned char). Why the difference?? Each is expecting the
same thing.

Not exactly the same thing. The one that works is looking for a pointer
to a
byte array. The second is looking for a byte array.

It sounds like you need to build a COM interop wrapper for the VB6 DLL.

--
HTH,

Kevin Spencer
Microsoft MVP
.Net Developer
Ambiguity has a certain quality to it.

Hello, all.

I've created a C# dll that contains, among other things, two functions
dealing with byte arrays. The first is a function that returns a byte
array, and the other is intended to receive a byte array as one of its
parameters. The project is marked for COM interop, and that all
proceeds normally.

When I reference the type library in the VB6 project, and write the
code to call the function that returns the byte array, it works
perfectly. However, when I write the code to call the method that
expects the byte arrary as a parameter, VB informs me that "Function is
marked restricted, or uses a type not supported by Automation." The
Object Browser shows the library AND the method with its (correct)
parameter list.

The latter portion of the warning didn't ring entirely true to me,
because I had returned a byte array with no problem. It occurred to me
there might be a problem on the outbound side.

As a test, I built a quick-and-dirty VB6 COM DLL with a method that
expected a byte array, and the OLEView utility for that DLL showed the
TLB for that method showed this signature:

VB6 dummy COM DLL method:
HRESULT TestMethod( [in] SAFEARRAY(unsigned char)* parm,
[out, retval] BSTR* pRetVal);

However, when I looked at the IDL for the Interop assembly of my C#
code, the Byte array parameter signature was as follows:

C# class method w/System.Byte[] as parameter
HRESULT MyMethod( [in] SAFEARRAY(unsigned char) FileContent,
[in] BSTR FileComment,
[out, retval] BSTR* pRetVal);

Note the difference in the signatures for the first parameter; the
first is [in] SAFEARRAY(unsigned char)*, but the latter is only [in]
SAFEARRAY(unsigned char). Why the difference?? Each is expecting the
same thing.

My initial thought was that this, perhaps, was a bug in the IDL/TLB
generation code from C#, but that seemed unlikely (?). Can anyone else
offer any suggestions or help?

Ultimately, I really just need to pass a byte array from VB6 to C#, so
I figure someone is bound to have done that before. If anyone has any
suggestions, I'd be appreciative.

Thanks,
David

ps Please reply to group; email herein is long since dead.
 
I

intrepid_dw

I'm sorry, Kevin. Bear with me. I really don't mean to confuse you :)

First, the C# DLL has two methods; one returns a byte array, one
requires a byte array as a parameter. A VB6 client needs to talk to
each of these methods, so I mark the C# DLL for COM Interop. So far, so
good.

Next, I fire up VB6 and include a reference to the .tlb from the
interop assembly. So far, so good.

Now, I write code to call the interop methods from VB6. When I call the
method that returns the array of bytes, it works. When I call the
method that requires a byte array as a parameter, I get a compile-time
error saying "Function marked as restricted or uses an automation type
not supported in Visual Basic."

I discard the first possibility (marked as limited). I don't understand
the second possibility, because if I can get an array of bytes from an
Interop assembly, why shouldn't I be able to send one? So, I suspect
it's some sort of type problem I don't understand or see.

Now, I examine the TLB of the Interop assembly, and the method that's
giving me problems has the byte array parameter declared as a
SAFEARRAY(unsigned char) - NOT a pointer.

Not recognizing the type problem, I opt to just try some research
separately. I wanted to see what the declaration for a byte array
parameter would look like in the type library for a COM DLL written
from a different source - eg, VB6. SO I did; I wrote a throwaway VB6
DLL, and declared a function with a byte array as a parameter. I
observed the type library for this DLL, and noticed that parameter is
declared as a POINTER to a SAFEARRAY(unsigned char).

My confusion is that, while I fully understand the DLL's are coming
from diffrent sources, each is exposing a COM interface to receive the
same kind of data - an array of bytes (SAFEARRAY). On that basis, I
would expect COM to force both sides (COM Interop from .NET or VB6 COM
DLL) to generate a consistent declaration for the same data type, but
it didn't. One COM DLL wants a *pointer* to SAFEARRAY, while the other
wants JUST a SAFEARRAY. I was just trying to understand why they were
different. If you're passing a variant array to a COM server,
regardless of whether that COM server originates in an interop DLL or
VB6, it would seem as though you should pass it the same way. It seems
to me that one of these ways will end up being wrong.

If that doesn't help explain my situation, it's my fault, and I
appreciate your attempt nonetheless.

-David
 
W

Willy Denoyette [MVP]

Following C# method signatures:

byte[] RetArr();

void GetArr(byte[] aParam1);

should translate into:

HRESULT RetArr([out, retval] SAFEARRAY(unsigned char)* pRetVal);

HRESULT GetArr([in] SAFEARRAY(unsigned char) aParam1);



in your typelib generated by regasm.exe. Are you sure you didn't pass the
arg by ref?

void GetArr(ref byte[] aParam1);

Willy.
 
K

Kevin Spencer

Don't worry about it, David. I've been a bit overworked lately. Just
finished a deadline and am getting ready to get some rest now! So, it could
be me!

Here's the problem I'm having with your methodology: You created a second
COM DLL to test against. Now, you claim that it has the same parameter type
as the first, but you don't seem to understand the difference between a
pointer to an array and an array. At least you haven't specifically said
that the first VB COM DLL most definitely takes a pointer to an array rather
than an array as a parameter. Since you've created a second VB6 COM DLL, I
can't be sure whether or not the method you defined in the second indeed
matches the signature of the method defined in the first. In other words,
this whole second DLL you created has done nothing but muddy the waters with
unreliable data. Unless you can tell me that you're absolutely certain that
both methods have the exact same signature, no test with the second VB6 DLL
will yield meaningful results.

The difference between an array of bytes and a pointer to an array of bytes
may not seem like much in VB, where everything is a variant, but it is
highly significant in the strongly-typed world of .Net. The difference is
the difference between my house, and the address of my house. Now, I might
say that I live at 13 Mockingbird Lane, but I don't really. I live in the
house that is located at the address of 13 Mockingbird Lane. The address is
merely a locator for my house. Let's say that I hired a mover and had my
entire house moved to another location. I would still live in my house, but
it would no longer be at 13 Mockingbird Lane.

In .Net, everything is really a pointer, underneath the hood. When you
create an instance of a string, for example, you are creating a pointer to
an immutable array of char. If you change the string, you aren't really
changing the string at all. You are creating a new string somewhere else in
memory, and moving the pointer with it. The pointer still has the same name,
and is a pointer to a string, but not a pointer to the SAME string.

So, if you pass an array, you are passing the array itself. When you pass a
pointer to an array, you are passing the address in memory of the array. A
pointer is a number (offset in memory). An array is a group of numbers. Not
the same thing at all.

And, as I mentioned earlier, in my last post, you can't always rely on the
..Net tools to write your Interop assemblies exactly correctly, for a number
of reasons.

What you need to do is to find out (from the original VB6 COM DLL) what data
type it is expecting, and make sure that your Interop assembly is passing
that exact data type to it. This may require some tweaking of the Interop
assembly.

--
HTH,

Kevin Spencer
Microsoft MVP
..Net Developer
Ambiguity has a certain quality to it.
 
I

intrepid_dw

Kevin:

Thanks again. I greatly appreciate the effort.

Please know that I understand the difference between pointers to arrays
and arrays and the like...I've been hacking around in all these things
for longer than I care to mention, from C to C++ and VB6 and .NET and
too many others, and have tried to mentor many a coworker on them over
the years :). This is just a very narrow problem tied to this
particular interop situation that made me wonder about typelib
generation for COM Interop DLL's.

I'm pretty sure that, based on your response, that I'm just doing a
lousy job of explaining my problem - and that's entirely my fault. I'm
more than a bit embarrassed to think how dense you must think I am to
have had to explain pointers...<gulp>....

Let me reset things a bit, along with some updated info. First, let me
offer that I fixed the original problem of VB6 complaining about the
"automation type unsupported by Visual Basic." In fixing *that*
problem, it ALSO fixed the way .NET generated the type library for the
Interop assembly. Those issues dovetail in a way that probably explains
the whole problem better than I have so far.

Here's how I got there. In thinking about the typelib for the COM
interop method, I realized what VB was complaining about; it
essentially saw a declaration requesting that a SafeArray be passed
ByVal, which almost certainly scrambled VB's brain. It can only pass
them by reference. That implies that the method signature needed to be
SAFEARRAY(unsigned char)*. I needed a "control" to verify the theory,
and that led me to building the throwaway VB dll that expected a byte
array parameter so I could inspect *its* type library...Sure enough,
the type library for a VB6 DLL expecting a byte array parameter ends up
looking like

HRESULT MyFunction([in, out] SAFEARRAY(unsigned char)* MyArray);

WIth that in hand, that led me to the fix on the .NET side.......

The fix was to change the method parameter signature in the C# Interop
DLL from "byte[]" to "ref byte[]". This had the downstream effect of
changing the method signature in the COM type library to
SAFEARRAY(unsigned char)*, which is *precisely* what I expected/needed.
When I call the function from VB, it now works perfectly.

A different way of seeing my problem can be illustrated in the
following C# example:

void SomeMethod(System.Byte[] lotsabytes, ref byte[] morebytes);

If marked for COM Interop, the type library for SomeMethod will look
something like:

HRESULT SomeMethod([in] SAFEARRAY(unsigned char) lotsabytes, [in]
SAFEARRAY(unsigned char)* morebytes)

VB can't pass an array by value, as implied by the first declaration,
but it can pass the array by reference. The latter declaration is
consistent with how .NET writes the COM Interop type library for a
function *returning* a byte array, but not for a *parameter*. That's
what had me puzzled; declarations for the same type were being written
two different ways. Changing the declaration of the array to "ref"
resolved the problem, and everything now works the way I
intended/expected.

I realize you're probably tired of my rambling explanations, but
hopefully THIS version of the explanation was an approach from a
different angle that clarified what I was trying to do. I appreciate
your patient and polite help nonetheless, and I hope you don't think
I'm a *complete* idiot. :)

David
 
I

intrepid_dw

Hi, Willy.

I just saw your post, right after I'd finished a rather lengthy reply
to Kevin's last message.

You're absolutely right - the typelibs for the methods you describe are
exactly what I'm seeing. For VB to send a byte array, which will
necessarily be by reference, the parameter for your "GetArr" sample
would have to be SAFEARRAY(unsigned char)* aParam1.

I changed the delcaration in my C# method to ref byte[], and that
solved the problem. The typelib now matches what I would expect for a
byte array, and the resultant C# DLL interops with VB6 perfectly.

Thanks for your willingness to help.

David
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top