0

In a legacy part of my application, I have the following COM code:

var plate = new Plate(); // this is the COM object

[..]

string dataSetName = string.Empty;
object row = null;
object column = null;
object kineticIndex = null;
object wavelengthIndex = null;
object horizontalIndex = null;
object verticalIndex = null;
object value = null;
object primaryStatus = null;
object secondaryStatus = null;

while(plate.GetRawData(
    ref dataSetName,
    ref row,
    ref column,
    ref kineticIndex,
    ref wavelengthIndex,
    ref horizontalIndex,
    ref verticalIndex,
    ref value,
    ref primaryStatus,
    ref secondaryStatus) != 0)
{
    var rawDataReader = new RawDataReader
    {
        DataSetName = dataSetName,
        Row = (int[])row,
        Column = (int[])column,
        KineticIndex = (int[])kineticIndex,
        WavelengthIndex = (int[])wavelengthIndex,
        HorizontalIndex = (int[])horizontalIndex,
        VerticalIndex = (int[])verticalIndex,
        Value = (double[])value,
        PrimaryStatus = (int[])primaryStatus,
        SecondaryStatus = (int[])secondaryStatus
    };
    [..]
}

Here is the GetRawData signature when explored with ole-com viewer:

[id(0x00000019), helpstring("method GetRawData")]
long GetRawData(
    BSTR* pDataSetName, 
    VARIANT* pvRow, 
    VARIANT* pvColumn, 
    VARIANT* pvKineticIndex, 
    VARIANT* pvWavelengthIndex, 
    VARIANT* pvHorizontalIndex, 
    VARIANT* pvVerticalIndex, 
    VARIANT* pvValue, 
    VARIANT* pvPrimaryStatus, 
    VARIANT* pvSecondaryStatus);

This code worked well for several years, even resisted to the migration from .NET 4.6.1 to .NET Core 3.1.

But lately we migrated to .NET 5 and it failed me: the first call to the GetRawData method completes successfully, but the second one throws the following exception:

Message:

System.Runtime.InteropServices.COMException : Type mismatch. (0x80020005 (DISP_E_TYPEMISMATCH))

Stack Trace:

RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)

RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Object[] aArgs, Boolean[] aArgsIsByRef, Int32[] aArgsWrapperTypes, Type[] aArgsTypes, Type retType)

IPlate.GetRawData(String& pDataSetName, Object& pvRow, Object& pvColumn, Object& pvKineticIndex, Object& pvWavelengthIndex, Object& pvHorizontalIndex, Object& pvVerticalIndex, Object& pvValue, Object& pvPrimaryStatus, Object& pvSecondaryStatus)

First of all, I was able to fix it by reseting all the parameters to null before the next call to the GetRawData method. So I am not here to get a fix but an explanation of what happened under the hood:

Why the assignment of a ref parameter pointing to null works, when the same call with a ref parameter pointing to an actual object failed?

As you can see in the while body, all the parameters are actually arrays of int or double. I am not very familiar with Interop but I don't understand why I could not assign anything to a ref object parameter, whatever its current value is.

So I write a little program without Interop to validate that it is actually possible in "vanilla" .NET 5:

class Program
{
    static void Main(string[] args)
    {
        object param = null;

        Test(ref param);

        Test(ref param);
    }

    static void Test(ref object param)
    {
        param = new int[12];
    }
} 

And it completes successfully. So I guess the problem is related to the Interop. According to the exception message (Type mismatch), I can imagine a start of explanation. But it is still unnatural to me:

  • Is it related to the way the owner implement the Interop dll?
  • Why does it fail suddently when migrating to .NET 5?
fharreau
  • 2,105
  • 1
  • 23
  • 46
  • How is defined GetRawData exactly (idl, .h, other information) and how it is implemented? – Simon Mourier Jun 15 '21 at 10:22
  • I know you are after an explanation and sadly I can't offer any but I see your code is still pre-NET 4 `dynamic` era. Try the newer syntax described in _[Cutting Edge - C# 4.0, the Dynamic Keyword and COM](https://learn.microsoft.com/en-us/archive/msdn-magazine/2010/june/msdn-magazine-cutting-edge-csharp-4-0-the-dynamic-keyword-and-com)_. The new style is much more forgiving and `dynamic` was actually designed for COM interop in mind –  Jun 15 '21 at 10:33
  • 1
    @MickyD - dynamic with COM is sadly broken, as many other COM stuff (because it's "Windows Only!") :-( with .NET 5: https://github.com/dotnet/runtime/issues/12587 – Simon Mourier Jun 15 '21 at 10:42
  • 1
    @SimonMourier you know I was going to say something about .NET 5 most likely being the cause but I was surprised that OP had something working. I didn't know about dynamic being broken. The more I hear about .NET 5 (no more App Domains, CAS, GAC, partially trusted code) the more disillusioned I am. Just like XNA Framework 4 before it, both have been re-written for the lowest-common denominator :( –  Jun 15 '21 at 11:27
  • @SimonMourier: unfortunatelly, I don't know how it is implemented since it is a proprietary dll (unless there is a way to determine it from the dll?). I just add the C# signature in the question. – fharreau Jun 15 '21 at 11:44
  • The signature is of little help. Is there any .tlb (the .tlb can be embedded as a Win32 resource into the .dll) – Simon Mourier Jun 15 '21 at 11:45
  • @MickyD : I just tried with the `dynamic` type as you suggest. But I get the same result: works for the first call, fails at the second. – fharreau Jun 15 '21 at 11:46
  • @SimonMourier: As I said in the question, I don't know anything about COM and Interop :D. I am not sure about what you asked for. I managed to open the original .exe following this answer: https://stackoverflow.com/a/23224572/2183236. Is it this file you want (https://ibb.co/cL8h8kG)? – fharreau Jun 15 '21 at 12:13
  • yes, it's an embedded tlb, just export that, save it as .tlb and you'll be able to see it using OleView (https://learn.microsoft.com/en-us/windows/win32/com/ole-com-object-viewer) and dump your method in your question. You can also use OleView directly on the binary (.exe or .dll) – Simon Mourier Jun 15 '21 at 15:14
  • @SimonMourier : I hope I just add what you asked for :D. (I had to install the windows SDK to get the OleView) – fharreau Jun 16 '21 at 08:26
  • 1
    In COM Automation, parameters like this have [in, out] semantics. In other words, the COM server has to do something untrivial when you pass a non-null argument. It has to first destroy the variant before assigning the intended value. Matters for the kind of variant that require storage (strings, arrays, interface pointers). That is not particularly complicated, VariantClear() gets the job done, but it appears that the author of this component fumbled that. No idea why of course, might be a heads-up about the very inefficient code, those arrays are particularly expensive to marshal. – Hans Passant Jun 16 '21 at 11:05
  • yes, unfortunately this doesn't tell us much more :-/ If you can't contact the component author, you basically have to live with it. – Simon Mourier Jun 17 '21 at 15:02
  • @SimonMourier : Well I can live with it since I found a workaround :D. Thank you anyway. – fharreau Jun 17 '21 at 15:40

0 Answers0