0

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?

Community
  • 1
  • 1
Corey Ross
  • 1,995
  • 1
  • 15
  • 16

3 Answers3

1

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
  • 5,622
  • 1
  • 28
  • 45
  • 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 Oct 14 '08 at 16:09
  • 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 Oct 14 '08 at 16:19
  • 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 Oct 14 '08 at 16:29
  • [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 Oct 14 '08 at 17:27
0

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

Gerald
  • 23,011
  • 10
  • 73
  • 102
0

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,995
  • 1
  • 15
  • 16