3
votes

I am using COM in my C# .NET project.
However one of the methods I call is not acting as expected.
So I am curious to see what is happening between my .NET code, the Interop layer and COM.
I know the tlbimp.exe generates the metadata wrapper for the COM component and I can see these generated methods in the Object browser.
Am I able to see/debug what happens when one of these wrapper methods is called?

I pass an Array to the method below, and expect that this array will be populated, however the Array does not get populated. I am calling the following tlbimp.exe generated method with unexpected results:

int GetTags(System.Array buffer)
    Member of CServer.IUser

Method IDL:

[id(0x000000d5)]
HRESULT GetTags(
                [in] SAFEARRAY(long) buffer, 
                [out, retval] long* retval);  

.NET code calling this method:

Array tagsArray = Array.CreateInstance(typeof(int), tagsLength);
userWrapper.GetTags(tagsArray);

Other COM methods I call work fine. However when I call any method which expects an Array as a parameter it does not work as expected.
I am presuming that there is something funny going in with the COM interop marshaller.
So I would like to know if I can see what is happening after I call the GetTags() method.

Also I have read the following here.

"if you are not satisified with the COM Interop marshaller, you can "override" just about every aspect of it through the very large and useful System::Runtime::InteropServices namespace"

How can I achieve the above?

EDIT: Adding a Delphi test script which works

procedure TComTestForm.TestUserBtnClick(Sender: TObject);
var
  nCnt :integer;
  User :IUser;
  Persona :IUserPersona;
  ArrayBounds :TSafeArrayBound;
  ArrayData :Pointer;
  TagList :PSafeArray;
  nSize :integer;
begin
  User := Session.GetUser;

  ArrayBounds.lLbound   := 0;
  ArrayBounds.cElements := 0;

  TagList := SafeArrayCreate( varInteger, 1, ArrayBounds );
  User.GetTags( TagList );
  if SafeArrayAccessData( TagList, ArrayData ) = S_OK then
    begin
      nSize := TagList.rgsabound[0].cElements;
      OutLine( '----Available Tags, ' + IntToStr(nSize) + ' tags' );
  for nCnt := 0 to nSize - 1 do
    begin
      OutLine( IntToStr( IntegerArray(ArrayData)[nCnt] ) );
    end;
  OutLine( '----');

  SafeArrayUnAccessData( TagList );
  SafeArrayDestroy( TagList );
    end;

end;
2
What kind of error do you have? What is not "working as expected"? - Simon Mourier
@Simon Mourier: Thanks for your response. I don't get any error. The method executes fine but the Array does not get populated. The COM code has been tested with delphi and works fine, so this leads me to believe there is something going on in the com-interop layer? - shane87
I think if you want to be able to populate the array in the COM layer, your COM prototype should be different. You need to define the SAFEARRAY by reference or add an [in, out] to the .IDL I suppose. The .NET Marshaler (as any other marshaler BTW) thinks it's a one way call only. - Simon Mourier
@Simon: Thank you again for your response. How would I define the SAFEARRAY by reference?. Also if I need to change the IDL does that mean recreating the COM dll? - shane87

2 Answers

2
votes

Another update: I just realize it might be that you mean that the GetTags itself should populate that Array (from the COM code). But this can never work as that parameter is an [in] parameter.

For the COM component to be able to fill up that Array, It should be passed as an [in, out] parameter, and by reference (SAFEARRAY*).


Update: Ok, apparently I was mixing the creation of a COM component in .NET with calling a COM component from .NET.

The CCW (com callable wrapper) indeed takes a .NET Array for COM SafeArray's. I see you create your array in the code in your question, but you don't show how you actually populate it. Maybe something's wrong with that code? Could you share it?


Not sure if this is a solution to your problem, but I've experienced problems with COM-interop and SAFEARRAY's in the past.

One thing I learned from it is that the .NET equivalent of a COM SAFEARRAY should always be object, so try passing your array as an object in stead of as an Array.

1
votes

I hesitate to suggest this as an answer, but...

If the Delphi test code really does work, as noted elsewhere, this means the GetTags method must not be playing properly by SAFEARRAY rules. If the COM method is always called in-process, you MAY be able to get the .NET code to work by doing some custom marshalling "by hand", following exactly what the unmanaged Delphi test code does.

As a rough outline, I would imagine that this would involve:

  • allocating an unmanaged buffer to hold the array values
  • calling Ole Automation SAFEARRAY initialization APIs via P/Invoke to allocate a SAFEARRAY structure and attach the array buffer to it as its pData member
  • invoking the GetTags method with this SAFEARRAY
  • marshalling your unamanaged buffer into a managed array, before...
  • calling the Win32 API to destroy the SAFEARRAY

But much better to get the COM component changed to do things properly, if you can.