C++Builder: using variant type variable to store Delphi interface

| category: My notes | author: st
Tags: ,

Delphi case

Delphi supports interfaces natively. Any variable of variant type can store an interface like any allowed data types.

program IntfDelphi;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Classes, SysUtils, Variants;

type
  ITest = interface(IInterface)
    ['{BEDC5E17-2C40-4F7C-8C78-34448F5CE146}']
    procedure Foo(const Msg: string);
  end;

  TestImpl = class(TInterfacedObject, ITest)
  public
    procedure Foo(const Msg: string);
  end;

procedure TestImpl.Foo(const Msg: string);
begin
  writeln('Foo(): ' + Msg);
end;

var
  Test: ITest;
  v: variant;
begin
  v := (TestImpl.Create as ITest);
  writeln('The type of "v" is ', VarTypeAsText(VarType(v)));
  if VarSupports(v, ITest, Test) then
    Test.Foo('from variant');
end.

The program output is as expected.

The type of "v" is UnknownFoo(): called from variant

C++Builder case

However, the similar C++Builder XE 10.x program has at least two problems (I checked it out with both 10.1 Berlin and 10.2 Tokyo):

  • the variant variable type is varDispatch instead of varUnknown expected type, so the VarSupports() function doesn't return an interface pointer;
  • the code doesn't compiles with clang compiler at all with following error "[bcc32c Error] main.cpp(28): no viable conversion from '_di_ITest' (aka 'DelphiInterface') to 'System::Variant'".
#include <vcl.h>
#pragma hdrstop
#pragma argsused
#include <tchar.h>
#include <stdio.h>

  __interface __declspec(uuid("{BEDC5E17-2C40-4F7C-8C78-34448F5CE146}"))
  ITest : public IInterface
  {
    public:
      virtual void Foo(const UnicodeString msg) = 0;
  };
  typedef System::DelphiInterface<ITest> _di_ITest;


  class TestImpl : public TCppInterfacedObject<ITest>
  {
    public:
      void Foo(const UnicodeString msg)
      {
        std::wcout << L"Foo(): " << msg << std::endl;
      }
  };


int _tmain(int argc, _TCHAR* argv[])
{
  System::Variant v = _di_ITest(new TestImpl());
  std::wcout << L"The type of 'v' is : " << VarTypeAsText(VarType(v)) << std::endl;
  _di_ITest intf;
  if (System::Variants::VarSupports(v, __uuidof(ITest), &intf))
    intf->Foo(L"from variant");
  else
    std::wcout << L"'v' doesn't support ITest" << std::endl;
  return 0;
}

The program output is not as expected!

The type of 'v' is : Dispatch
'v' doesn't support ITest

Workaround 1 (bcc32 only)

The bug RSP-18746 was reported to Embarcadero using this workaround for the 'classic' compiler. You should declare varaint type variable before and initialize it at the next line.

System::Variant v;
v = _di_ITest(new TestImpl());

Surprisingly, it works!

The type of 'v' is : Unknown
Foo(): from variant

However, the clang compiler still produces the same error.

Workaround 2 (both 'classic' and clang compilers)

As the internal structure of the varaint type is known and well documented, you can initialize it directly. Don't forget to increment the reference counter.

System::Variant v;
v.VType = varUnknown;
TestImpl* p = new TestImpl();
p->AddRef();
v.VUnknown = System::interface_cast<ITest>(p);

However, a little more code is required for this workaround. You can wrap it into a small factory-like function of the TestImpl class.

static System::Variant TestImpl::NewIntfToVar()
{
  System::Variant v;
  v.VType = varUnknown;
  TestImpl* p = new TestImpl();
  p->AddRef();
  v.VUnknown = System::interface_cast<ITest>(p);
  return v;
}

Use the factory method to write the code on one line.

System::Variant v = TestImpl::NewIntfToVar();

The program produces the output as expected.

The type of 'v' is : Unknown
Foo(): from variant