Skip to content

Conversation

@anpilog
Copy link

@anpilog anpilog commented Oct 19, 2021

This PR is based on #141 and #142
PR is in draft state so far but fully illustrate an approach that allows using native RTTI support.

In a few words the PR addresses missed part of ACT - original type of an object that has been passed from library to application side.

Let's look on an example.
AnimalIterator class has a method GetNextAnimal() method that returns instance of Animal.
image
But in fact, actual object could be any of derived animal classes.
So the signature of the method is defined. And this is what ACT generates today:
image
It creates shared pointer to an instance of Animal class. Just like defined in IDL file.
However, this is not what actually happens under the hood. Animal objects on library side belong to other classes.
This information just gets lost through ACT because it passes objects as a typeless handle.

IMHO, ideally, class type should be just a part of Handle type. Instead of void * it could be a struct with two fields: void* and a classId.
But in this PR for a simplicity of reviewing the idea I leave handle as void * and just add class type id.

Each class has unique Class Type Id in a form of 64 bits unsigned integer. It's generated as 64 most significant bits of SHA1 for a string NameSpace::ClassName, where NameSpace is a namespace defined in IDL file and ClassName is an actual name of a given class.

Base class has a virtual method uint64 ClassIdType(). In C++ implementation it belongs to interfaces layer and thus completely hidden from developers.
Actual name is configured in IDL by global setting classtypeidmethod. It's autogenerated by ACT and returns class type id.

On a bindings side (application) ACT implements hidden from developer PolymorphicFactory() function that is used to instantiate objects from handle. Instead of std::make_shared on C++, for example.
It serves only one goal - instantiate correct class on an application side that matches class of a handle on a library side.
C++ example:

template <class T>
std::shared_ptr<T> CWrapper::polymorphicFactory(RTTIHandle pHandle)
{
	RTTI_uint64 resultClassTypeId = 0;
	CheckError(nullptr, m_WrapperTable.m_Base_ClassTypeId(pHandle, &resultClassTypeId));
	switch(resultClassTypeId) {
		case 0x1549AD28813DAE05UL: return std::dynamic_pointer_cast<T>(std::make_shared<CBase>(this, pHandle)); break; // First 64 bits of SHA1 of a string: "RTTI::Base"
		case 0x8B40467DA6D327AFUL: return std::dynamic_pointer_cast<T>(std::make_shared<CAnimal>(this, pHandle)); break; // First 64 bits of SHA1 of a string: "RTTI::Animal"
		....
	}
	return std::make_shared<T>(this, pHandle);
}

and then used instead of std::make_shared in class method implementation for class parameters passed as out or return.

/**
* CAnimalIterator::GetNextAnimal - Return next animal
* @return 
*/
PAnimal CAnimalIterator::GetNextAnimal()
{
    RTTIHandle hAnimal = nullptr;
    CheckError(m_pWrapper->m_WrapperTable.m_AnimalIterator_GetNextAnimal(m_pHandle, &hAnimal));
    
    if (hAnimal) {
        return m_pWrapper->polymorphicFactory<CAnimal>(hAnimal);
    } else {
        return nullptr;
    }
}

I already mentioned above that direct call to ClassTypeId() method to retrieve object class is not optimal and could be optimized.
It's better passing class type with a handle. Either as part of Handle type or by extra parameter of a C function call.

This PR includes implementation for C++, Python and Pascal bindings and C++ with Pascal Stubs.

On an application side code relies on a language native capabilities:
C++

while (auto animal = iter->GetNextAnimal()) {
	std::cout << "Animal name: " << animal->Name() << std::endl;
	if (auto tiger = std::dynamic_pointer_cast<CTiger>(animal)) {
		std::cout << "  ^ is a real tiger!!!" << std::endl;
		tiger->Roar();
	}
}

Python

while animal := iter.GetNextAnimal():
    if isinstance(animal, RTTI.Tiger):
        print("  ^ is a real tiger!!!")
        animal.Roar()
    print("Animal name: " + animal.Name())

Pascal:

Animal := Iterator.GetNextAnimal();
while Animal <> nil do
begin
    writeln('Animal name: ', Animal.Name());
    if Animal is TRTTITiger then 
    begin
        writeln('  ^ is a real tiger!!!');
        Tiger := Animal as TRTTITiger;
        Tiger.Roar();
    end;
    Animal := Iterator.GetNextAnimal();
end;

NOTES and TODO:

  • Check functionality in case of injected components - done at RTTI: typed object handle anpilog/AutomaticComponentToolkit#1
  • String that is used for calculating SHA1 hash should include whole hierarchy of parent classes. Theoretically, library can change data model without breaking ABI. And in this case it's safer to ignore RTTI and instantiate explicitly defined class in IDL. Looks like it's not necessary as objects are always resolved in corresponded PolymorphicFactory.

@martinweismann
Copy link
Member

Now covered by #182

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants