tags:

views:

811

answers:

3

I am working with an existing code base made up of some COM interfaces written in C++ with a C# front end. There is some new functionality that needs to be added, so I'm having to modify the COM portions. In one particular case, I need to pass an array (allocated from C#) to the component to be filled.

What I would like to do is to be able to pass an array of int to the method from C#, something like:

// desired C# signature
void GetFoo(int bufferSize, int[] buffer);

// desired usage
int[] blah = ...;
GetFoo(blah.Length, blah);

A couple of wrenches in the works:

  • C++/CLI or Managed C++ can't be used (COM could be done away with in this case).
  • The C# side can't be compiled with /unsafe (using Marshal is allowed).

The COM interface is only used (an will only ever be used) by the C# part, so I'm less concerned with interoperability with other COM consumers. Portability between 32 and 64 bit is also not a concern (everything is being compiled and run from a 32 bit machine, so code generators are converting pointers to integers). Eventually, it will be replaced by just C++/CLI, but that is a ways off.


My initial attempt

is something similar to:

HRESULT GetFoo([in] int bufferSize, [in, size_is(bufferSize)] int buffer[]);

And the output TLB definition is (seems reasonable):

HRESULT _stdcall GetFoo([in] int bufferSize, [in] int* buffer);

Which is imported by C# as (not so reasonable):

void GetFoo(int bufferSize, ref int buffer);

Which I could use with

int[] b = ...;
fixed(int *bp = &b[0])
{
    GetFoo(b.Length, ref *bp);
}

...except that I can't compile with /unsafe.


At the moment

I am using:

HRESULT GetFoo([in] int bufferSize, [in] INT_PTR buffer);

Which imports as:

void GetFoo(int bufferSize, int buffer);

And I need use use it like:

int[] b = ...;
GCHandle bPin = GCHandle.Alloc(b, GCHandleType.Pinned);
try
{
    GetFoo(b.Length, (int)Marshal.UnsafeAddrOfPinnedArrayElement(b, 0));
}
finally
{
    bPin.Free();
}

Which works..., but I'd like to find a cleaner way.


So, the question is

Is there an IDL definition that is friendly to the C# import from TLB generator for this case? If not, what can be done on the C# side to make it a little safer?

A: 

I don't know much about C# COM operability, but have you tried using SAFEARRAY(INT_PTR) or something similar?

Gerald
I'll look into that
Corey Ross
A: 

Hmmm... I've found some information that gets me closer...

Marshaling Changes - Conformant C-Style Arrays

This IDL declaration (C++)

HRESULT GetFoo([in] int bufferSize, [in, size_is(bufferSize)] int buffer[]);

Is imported as (MSIL)

method public hidebysig newslot virtual instance void GetFoo([in] int32 bufferSize, [in] int32& buffer) runtime managed internalcall

And if changed to (MSIL)

method public hidebysig newslot virtual instance void GetFoo([in] int32 bufferSize, [in] int32[] marshal([]) buffer) runtime managed internalcall

Can be used like (C#)

int[] b = ...;
GetFoo(b.Length, b);

Exactly what I was gunning for!

But, are there any other solutions that don't require fixing up the MSIL of the runtime callable wrapper that is generated by tlbimport?

Corey Ross
+1  A: 

So you're asking for an IDL datatype that is 32-bits on a 32-bit machine and 64-bits on a 64-bit machine. But you don't want the marshaling code to treat it like a pointer, just as an int. So what do you expect to happen to the extra 32-bits when you call from a 64-bit process to a 32-bit process?

Sound like a violation of physics to me.

If it's inproc only, see the bottom of this discussion: http://www.techtalkz.com/vc-net/125190-how-interop-net-client-com-dll.html.

The recommendation seems to be to use void * instead of intptr and flag with the [local] so the marshaller doesn't get involved.

Tony Lee
I would like them all to be INT_PTR, however tlbimport is converting them to int, not me. If I do need to go the MSIL fixup route, I can correct it to System.IntPtr there.
Corey Ross
What I don't get is why you're using INT_PTR when your solution will only work in a 32-bit process?
Tony Lee
The COM side is definitely 32bit, so initially that signature was chosen by following similar conventions to existing code. But, you are right about the managed side, I should explicitly specify a 32bit int (which tlbimport is doing).
Corey Ross
[local] void* does import as System.IntPtr, but the other issues still exist.Unfortunately this 32/64 bit portability tangent doesn't address the C-style array semantics that I am trying to achieve.
Corey Ross