What is the correct way to pass a COM Interface back to a library?

3

I get an System.InvalidCastException runtime error when trying to pass an interface as a parameter from .NET to a DLL. The code fails on the .NET side and never makes it over to Delphi side.

Construct a simple ActiveX library dll in Delphi. Two Automation Objects: MyContent and MyContainer. MyContainer has single method Add that takes an IBase interface that is inherited by IMyContent. IBase is an IDispatch

[
  uuid(E29018FF-F142-4BAE-B7A4-AE0A8847E930),
  version(1.0)

]
library DelphiComLib
{

  importlib("stdole2.tlb");

  interface IMyBase;
  interface IMyContainer;
  coclass MyContainer;
  interface IMyContent;
  coclass MyContent;


  [
    uuid(07D17021-9E7F-4D7A-B861-59A35EC686A0),
    dual,
    oleautomation
  ]
  interface IMyBase: IDispatch
  {
  };

  [
    uuid(A6AB6F7D-8BEB-459F-A2F8-BC06FF81A45D),
    helpstring("Dispatch interface for MyContainer Object"),
    dual,
    oleautomation
  ]
  interface IMyContainer: IDispatch
  {
    [id(0x000000C9)]
    HRESULT _stdcall Add([in] IMyBase* AMyBase);
  };

  [
    uuid(AB82964C-13D7-423B-9B16-A789D5D30421),
    helpstring("Dispatch interface for MyContent Object"),
    dual,
    oleautomation
  ]
  interface IMyContent: IMyBase
  {
  };

  [
    uuid(DDDF77E5-E6A6-4429-BD4A-D9695E9E6CED),
    helpstring("MyContainer Object")
  ]
  coclass MyContainer
  {
    [default] interface IMyContainer;
  };

  [
    uuid(134BF8B2-30C3-4C37-8F74-3F677808300A),
    helpstring("MyContent Object")
  ]
  coclass MyContent
  {
    [default] interface IMyContent;
  };

};

The implementation of the classes does not matter. To keep the sample minimal the implementation of Add can be left blank. Here is the Container

unit Unit1;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  ComObj, ActiveX, DelphiComLib_TLB, StdVcl;

type
  TMyContainer = class(TAutoObject, IMyContainer)
  protected
    procedure Add(const AMyBase: IMyBase); safecall;

  end;

implementation

uses ComServ;

procedure TMyContainer.Add(const AMyBase: IMyBase);
begin

end;

initialization
  TAutoObjectFactory.Create(ComServer, TMyContainer, Class_MyContainer,
    ciMultiInstance, tmApartment);
end.

... and the Content

unit Unit2;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  ComObj, ActiveX, DelphiComLib_TLB, StdVcl;

type
  TMyContent = class(TAutoObject, IMyContent)
  protected

  end;

implementation

uses ComServ;

initialization
  TAutoObjectFactory.Create(ComServer, TMyContent, Class_MyContent,
    ciMultiInstance, tmApartment);
end.

Register the library and add a reference to a console application. The following code will produce the runtime error

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DelphiComLib;
namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            IMyContainer container = new MyContainer();
            IMyContent content = new MyContent();
            container.Add(content);


        }
    }
}

If I create a class in .NET that implements IBase I can pass interface back successfully.

Here is the StackTrace from .NET

   at System.StubHelpers.InterfaceMarshaler.ConvertToNative(Object objSrc, IntPtr itfMT, IntPtr classMT, Int32 flags)
   at DelphiComLib.IMyContainer.Add(IMyBase AMyBase)
   at ConsoleApplication2.Program.Main(String[] args) in c:\users\jaspers\documents\visual studio 2015\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:line 15
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
c#
delphi
com
delphi-10.1-berlin
asked on Stack Overflow Sep 13, 2016 by Jasper Schellingerhout • edited Sep 13, 2016 by Jasper Schellingerhout

1 Answer

2

Your problem has nothing to do with how the interface is passed to your COM DLL.

The error System.InvalidCastException is raised simply because casting from IMyContent to IMyBase fails on the Delphi implementation side. The reason is that strictly speaking your implementation does not directly implement IMyBase interface (even though it implements the derived IMyContent interface). You can try it:

var
  MyContent: IMyContent;
  MyBase: IMyBase;
begin
  MyContent := CoMyContent.Create;
  MyBase := MyContent as IMyBase; // EIntfCastError is raised
end;

Your implementing class must implement IMyBase explicitly for the casting to work:

Implement all interfaces explicitly

which will change the declaration of your implementing class:

type
  TMyContent = class(TAutoObject, IMyContent, IMyBase)
  ...
  end;

which in turn will make the casting work for any COM client code (Delphi, C++, .NET, etc.)

answered on Stack Overflow Sep 14, 2016 by Ondrej Kelle • edited Sep 14, 2016 by Ondrej Kelle

User contributions licensed under CC BY-SA 3.0