C++Builder: using variant type variable to store Delphi interface
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 ofvarUnknown
expected type, so theVarSupports()
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
blog comments powered by Disqus