What happens between the .NET interop layer and COM? - c#

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;

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.

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.

Related

Passing safearray of unmanaged structs between managed and unmanaged code

I have defined a struct MyStructure at IDL of unmanaged library SomeLibrary. I need to call managed code (C#) MyManagedLibPtr->RetrieveStuff from unmanaged library (C++) to fill and retrieve an array of these structs back for caller. The problem is that I haven't been able to figure out the signature of RetrieveStuff at managed side. I guess some custom marshalling is required? Here's what I have:
IDL of "SomeLibrary":
[
uuid(xxxxx-xxxx-xxxx-xxxx-xxxxx)
]
struct MyStructure
{
[helpstring("Some string values")] SAFEARRAY(BSTR) moValues;
[helpstring("Some other value")] BSTR moValue;
};
Unmanaged code (caller):
SAFEARRAY* saArray = NULL;
MyManagedLibPtr->RetrieveStuff(&saArray); // <--This is the key part
// The rest is just parsing the results.
// Using SafeArray -wrapper class that handles access/unaccess/etc..
SafeArray<SomeLibrary::MyStructure, VT_RECORD> oResults(saArray);
for (int i =0; i < oResults.GetSize(0); i++)
{
SomeLibrary::MyStructure oStruct = oResults[i];
// Etc......
}
At C# side, I've tried a few different solutions but none of them have been correct. This one would have been the sweetest, but obviously the mashalling automation wasn't sweet enough:
// Interface
[DispId(123)]
void RetrieveStuff(ref SomeLibrary.MyStructure[] roResultArray);
The error I get is some HRESULT -code. Haven't checked which one in particular, but obviously it's caused by incorrect signature or marshalling. Any help?
So, I found an answer that's ALMOST as requested. For whatever reason I couldn't get it working with ref parameter but I was able to do the marshalling with out. So this isn't 100% answer for the problem but damn close to it and will probably help others as well.
The correct interface declaration at managed side:
// Interface
[DispId(123)]
void RetrieveStuff([Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_RECORD)] out SomeLibrary.MyStructure[] roResultArray);

IIS Handler, Delphi to C# conversion

I am currently converting a Delphi application to C# for an ISS Handler.Delphi uses these memory classes (TMemoryStream, TStreamAdapter) to pass to methods, insert string values, and return and retrieve values from memory.
My question here is, do i really need to create memory objects to insert string values and pass them through methods for retrieval, or is this just unique to the Delphi handler. Currently i am just passing and retrieving string values in my ISS handler, will this be a sort of correct conversion.
Ive researched and came up short. Your help is kindly appreciated.
for eg Delphi code:
m := TMemoryStream.Create;
iss := TStreamAdapter.Create(m, soOwned);
iss._AddRef;
try
hr := CAGetPath(cas, cam, cal, iss);
cb := 0;
m.Write(cb, sizeof(WideChar));
s := PWideChar(m.Memory);
finally
iss._Release;
end;
function CAGetPath(SubscriberID, MailshotID, LinkID: integer;
stmPath: ISequentialStream): HRESULT; stdcall;
{
sPath: WideString;
sPath := "\\MYPATH\\TO\\FOLDER"
stmPath._AddRef;
cb := length(sPath) * sizeof(WideChar);
ES := 'stmPath.Write(' + sPath + ')';
Result := stmPath.Write(PWideChar(sPath), cb, nil);
}
This bit of delphi code just returns a string and sets it to variable s. As fas as i can tell. Is this neccessary and why would anyone do it this way?
Do I really need to create memory objects to insert string values and pass them through methods for retrieval, or is this just unique to the Delphi handler?
The author of the Delphi code is best placed to explain why it was done in that particular way. However, it looks to me as though CAGetPath is an external function since it was declared using stdcall. And so the author will have needed to come up with a solid means to marshal text data across a module boundary. The author chose to use the COM ISequentialStream interface which is a perfectly reasonable choice.
In order to interact with that from Delphi, the author needed to use an object that implements ISequentialStream. The simplest way is to use the TStreamAdapter class which wraps a TStream and presents an IStream interface. In order to use that, a concrete stream must be provided. Hence the use of TMemoryStream. Clearly CAGetPath must return the URL somewhere and why not a memory stream?
Anyway, that's my best guess as to why the Delphi code is that way. There's no evidence that memory streams are needed to implement IIS handlers (whatever they are).
I think you are getting all hung up on replicating the Delphi implementation. In your shoes I would simply try to understand what the underlying requirements are. What is your IIS handler required to do? Then implement that using the idiomatic C# techniques and classes. Use the extant Delphi code as a guide of what the requirement is, but not as a guide for how to implement that requirement.

C# Application crashes on method call to COM Class

I have a C# Winforms application that makes a call to a COM class. When debugging using Visual Studio it steps into the c++ code and returns S_OK but when the function returns visual studio hangs and the application crashes. I have to end process on the VS process to stop the program from running. If i run the app outside of visual studio the application simply crashes.
Everything was working fine and i have no idea what i may have done to cause this problem.
Any help is appreciated. Thanks
Sj
This is the interface definition
typedef struct
{
long ExpiryData
BSTR IssuedBy;
} LicenceData;
[
object,
uuid (5A734F95-EABE-440B-8B7E-0F73538A24AC),
pointer_default(unique),
helpstring("ILicenceReader Interface"),
]
interface ILicenceReader : IDispatch
{
HRESULT ReadLicenceFile ([in, out]LicenceData* plicenceData, LPCTSTR filePath);
};
[
uuid(C2833A21-6586-4648-ABC8-D42BC3225699)
]
coclass LicenceReader
{
[default] interface ILicenceReader;
};
I have referenced the COM dll and allowed VS to generate the Interop and the usage in the c# application:
LicenceData data = new LicenceData();
ILicenceReader reader = new LicenceReader();
reader.ReadLicenceFile(ref data, filePath);
Thanks for your help.
I'll bet that the COM subsystem is trying to unmarshall a BSTR allocated on the stack, or perhaps allocated with a smart pointer on the stack.
BSTRs have to be allocated with SysAllocString. The result from this can be returned as-is as it's not on the stack and nothing will try to free it erroneously.
If you use a smart pointer BSTR class such as CComBSTR or _bstr_t then you'll need to set the IssuedBy member via a Detach. CComBSTR::Detach() will return the pointer to the BSTR and not try to free it when that local instance of CComBSTR goes out of scope.
plicenceData->IssuedBy = CComBSTR("Some Dude").Detach();
Another possibility is that you try to do something like plicenceData = new plicenceData within your COM class, overwriting the instance passed in. That won't work.
In the end, pretty much the only reason a COM function fails after it's finished and returned is because of marshalling issues. It's the layer between your C# code and the called C++ that's trying to translate the data across apartment and possibly process boundaries. You need to ensure that you follow the COM rules to the letter to allow marshalling to do it's job.
So, double check all your pointers. Are they on the stack or on the heap? They need to be on the heap. Are all BSTRs allocated appropriately? Using smart BSTR classes will usually help significantly, but just remember that you can't return the raw members. Use these classes as they're expected to be used.

COM SAFEARRAY of GUID's returned from C++ to C#

I'm currently running into an issue of needing to pass a SAFEARRAY(GUID) as a return value from C++ to C#.
Currently the C# side is using an Interop dll generated from Tlbimp.exe (Type Library Importer).
The IDL is:
HRESULT GetGuids(
[out]SAFEARRAY(GUID)* guids);
I've also tried [out, retval]
The function signature is:
HRESULT
WINAPI
MyClass::GetGuids(SAFEARRAY** guids)
If I use SafeArrayCreate() or SafeArrayCreateVector():
SAFEARRAY* psa
psa = SafeArrayCreate(VT_CLSID, 1, rgsabound);
I get a NULL SAFEARRAY pointer, which is supposed to indicate E_OUTOFMEMORY which is incorrect.
What I did find was that VT_CLSID is only for Ole property sets and not SAFEARRAY's:
http://poi.apache.org/apidocs/org/apache/poi/hpsf/Variant.html Its indicated that CLSID is
I've also tried the alternate means of constructing the safe array with:
SafeArrayAllocDescriptor() and SafeArrayAllocData().
hResult = SafeArrayAllocDescriptor(1, guids)
hResult = SafeArrayAllocData(*guids);
This lets me create the array, but when populating it with SafeArrayPutElement() I get an HRESULT of 0x80070057 (The parameter is incorrect). This is probably due to the fact it takes the VT_CLSID parameter as well
I can populate it manually with SafeArrayAccessData()
GUID* pData = NULL;
hResult = SafeArrayAccessData(*guids, (void**)&pData);
but I get an error from the C# side:
"The value does not fall within the expected Range"
I'm not sure how to accomplish the desired functionality of returning a SAFEARRAY(GUID) to C# either by a retval or out parameter.
It seems it should be simple - there are many areas in the IDL where I'm already passing GUID's without any UDT's or marshalling. Everything works fine until I need to pass them in a SAFEARRAY.
Any help is appreciated,
Thanks in advance
You're absolutely right - the problem is that VT_CLSID isn't allowed in either VARIANT or SAFEARRAY. It boils down to GUID not being an Automation-compatible type.
I often need to do the same thing that you're trying. The easiest way around the problem is to convert the GUID to a string and then pass SAFEARRAY(VT_BSTR). It goes against the grain somewhat to do this conversion, but I suppose you could take the view that there's marshaling going on anyway and this conversion is a type of marshaling.
The way to do it involves passing GUIDs as a UDT (user defined type).
For that, we use a SAFEARRAY of VT_RECORD elements which will be initialized with SafeArrayCreateEx. But first, we have to get a pointer to IRecordInfo that can describe the type.
Since GUID is defined in the windows/COM headers and has no uuid attached to it, we have to use something else to get an IRecordInfo interface. Basically, the two options are to create a struct that has the same memory layout as GUID in your own TypeLib, or use mscorlib::Guid defined in mscorlib.tlb
#import <mscorlib.tlb> no_namespace named_guids
IRecordInfo* pRecordInfo = NULL;
GetRecordInfoFromGuids( LIBID_mscorlib, 1, 0, 0, __uuidof(Guid), &pRecordInfo );
SafeArrayCreateEx( VT_RECORD, 1, &sab, pRecordInfo );

Pass C# string to C++ and pass C++ result (string, char*.. whatever) to C#

I tried different things but i'm getting mad with Interop.
(here the word string is not referred to a variabile type but "a collection of char"):
I have an unmanaged C++ function, defined in a dll, that i'm trying to access from C#, this function has a string parameter and a string return value like this:
string myFunction(string inputString)
{
}
What should be string in C++ side? and C# one? and what parameters need DllImport for this?
What I've found to work best is to be more explicit about what's going on here. Having a string as return type is probably not recommended in this situation.
A common approach is to have the C++ side be passed the buffer and buffer size. If it's not big enough for what GetString has to put in it, the bufferSize variable is modified to indicate what an appropriate size would be. The calling program (C#) would then increase the size of the buffer to the appropriate size.
If this is your exported dll function (C++):
extern "C" __declspec void GetString( char* buffer, int* bufferSize );
Matching C# would be the following:
void GetString( StringBuilder buffer, ref int bufferSize );
So to use this in C# you would then do something like the following:
int bufferSize = 512;
StringBuilder buffer = new StringBuilder( bufferSize );
GetString( buffer, ref bufferSize );
The only good way that I know of doing this is to write a .NET C++ wrapper class using Managed C++ Extensions, and within the .NET C++ object call your native C++ code. There are functions in the managed extensions to convert a System.String to a char* or any other type of unmanaged string.
Basically you create a .NET class using C++ and expose it from an assembly, and internally to that assembly you can call your native C++ code. The other way is to add a pure C function to your C++ code using P/Invoke and then call your C code from C# and have your C function call your C++ code. This will work, but I tend to try to use managed code as much as possible.
The biggest problem with passing strings from C++ back to C# is the memory allocation. The GC should be able to know how to cleanup the memory allocated for this string. Since C# has extensive COm interop support, it does know about COM BSTRs and how to allocate and deallocate these. Thus the easiest way to do this would be to use BSTR on the C++ side and string on the C# side.
Note, using BSTRs does not imply that your function has to be expose through COM.
The "string" return value is the problem. The P/Invoke marshaller is going to call CoTaskMemFree() on the pointer you return. That's not going to work well unless you used CoTaskMemAlloc() in your C/C++ code to allocate the string buffer. Which is a fairly unusual thing to do.
The best solution is to allow the caller of your code to pass a pointer to a buffer and the buffer length to you as arguments. That way all memory allocation happens on one side. Scott showed you how to do this.
I had to convert a unicode C# string to a multibyte representation in order to convert to char* in c++ (this is partial one way solution)
I found the following very useful
string st;
IntPtr stPtr = Marshal.StringToHGlobalAnsi(st);
// Do your thing in C++
Marshal.FreeHGlobal(stPtr);
This may be inefficient and not in C# manner, I'm new to C#.

Categories