Search code examples
objective-cprotocolsdjinni

djinni created objective-c protocol not working


I'm using djinni to create cross platform code between c++, java and objective c because I'm currently moving a lot of code to a cross platform c++ implementation.

The problem is, that I have an interface that is a protocol in objective c. Another interface, that is implemented in c++ gets hte implementation of this protocol as a parameter and then calles the method. But this method is not called at all in objective c. (Better swift, but I tried also to implement this protocol in objective c, does not work). I have investigated into a bit more, but first my code.

To see if my issue are caused from the existing code, I created a test project, but it still like that:

So this is my idl file for djinni:

DataProvider = interface +j +o {
    const getContentForUrl(url: string): string;
}

DataUser = interface +c {
    static create(url: string, dataProvider: DataProvider): DataUser;
    getContent(): string;
}

This are the created c++ files:

DataProvider.hpp:

// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from htmlparser.djinni

#pragma once

#include <string>

namespace testdjinni {

class DataProvider {
public:
    virtual ~DataProvider() {}

    virtual std::string getContentForUrl(const std::string & url) = 0;
};

}  // namespace testdjinni

DataUser.hpp:

// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from htmlparser.djinni

#pragma once

#include <memory>
#include <string>
#include <vector>

namespace testdjinni {

class DataProvider;

class DataUser {
public:
    virtual ~DataUser() {}

    static std::shared_ptr<DataUser> create(const std::string & url, const std::shared_ptr<DataProvider> & dataProvider);

    virtual std::string getContent() = 0;

};

}  // namespace destdjinni

This is my DataUser interface and implementation in C++:

DataUserImpl.h:

#ifndef TESTDJINNI_DATAUSER_H
#define TESTDJINNI_DATAUSER_H

#include <string>
#include "generated-src/cpp/DataUser.hpp"

using namespace std;

class DataUserImpl : public testdjinni::DataUser{

private:
    string *content;
    testdjinni::DataUser *dataUser;

public:
    DataUserImpl(const string &url, testdjinni::DataProvider *dataProvider);

    virtual string getContent();

};


#endif //TESTDJINNI_DATAUSER

DataUserImpl.cpp:

#include <iostream>
#include "DataUserImpl.h"
#include "generated-src/cpp/DataProvider.hpp"

DataUserImpl::DataUserImpl(const string &url, testdjinni::DataProvider *dataProvider): dataProvider(dataProvider) {
    this->url = new string(url);
}

HtmlDocumentImpl::~HtmlDocumentImpl() {
    delete url;
}

std::shared_ptr<testdjinni::DataUser> testdjinni::DataUser::create(const std::string & url, const std::shared_ptr<testdjinni::DataProvider> & dataProvider) {
    return std::shared_ptr<testdjinni::DataUser>(new DataUserImpl(url, dataProvider.get()));
}

string DataUserImpl::getContent() {
if (this->content == NULL || *this->content == "")
    this->content = new string(this->getDataProvider()->getContentForUrl(*this->url));
    return *this->content;
};

Now I have this code in swift (I also tried the same in Objective C, but did not work):

class Provider: HPDataProvider {
    func getContentForUrl(_ url: String) -> String {
        return "Testcontent und so"
    }
}

class ViewController: UIViewController {

    let provider = Provider()

    override func viewDidLoad() {
        super.viewDidLoad()

        let user = HPDataUser.create("http://www.testurl.com", dataProvider: provider)

        print(user?.getContent())
    }
}

The problem is, that the pethod getContentForUrl in the implemented protocol is not called at all. I get a crash:

Assertion failed: (string), function toCpp, file /Path/to/my/project/deps/djinni/support-lib/objc/DJIMarshal+Private.h, line 119.

So what I can say now is, that the string seems to be null and my protocol implementation was not called.

Ok, so I got down to see what happens at all:

In the generated file for DataProvider+Private.mm that looks like this:

// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from htmlparser.djinni

#import "HPDataProvider+Private.h"
#import "HPDataProvider.h"
#import "DJIMarshal+Private.h"
#import "DJIObjcWrapperCache+Private.h"
#include <stdexcept>

static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for this file");

namespace djinni_generated {

class DataProvider::ObjcProxy final
: public ::htmlparser::DataProvider
, private ::djinni::ObjcProxyBase<ObjcType>
{
    friend class ::djinni_generated::DataProvider;
public:
    using ObjcProxyBase::ObjcProxyBase;
    std::string getContentForUrl(const std::string & c_url) override
    {
        @autoreleasepool {

            // Here, the objcpp_result is null, even if the object was not null, but my implementation of the protocol was never called.

            auto objcpp_result_ = [djinni_private_get_proxied_objc_object()  getContentForUrl:(::djinni::String::fromCpp(c_url))];
            return ::djinni::String::toCpp(objcpp_result_);
        }
    }
};

}  // namespace djinni_generated

namespace djinni_generated {

auto DataProvider::toCpp(ObjcType objc) -> CppType
{
    if (!objc) {
        return nullptr;
    }
    return ::djinni::get_objc_proxy<ObjcProxy>(objc);
}

auto DataProvider::fromCppOpt(const CppOptType& cpp) -> ObjcType
{
    if (!cpp) {
        return nil;
    }
    return dynamic_cast<ObjcProxy&>(*cpp).djinni_private_get_proxied_objc_object();
}

}  // namespace djinni_generated

I am clueless what I can do here... I mean, this is just basic code...


Solution

  • Errors that you experiencing are due to improper use of shared_ptr.

    std::shared_ptr<testdjinni::DataUser> testdjinni::DataUser::create(const std::string & url, const std::shared_ptr<testdjinni::DataProvider> & dataProvider) {
        return std::shared_ptr<testdjinni::DataUser>(new DataUserImpl(url, dataProvider.get()));
    }
    

    You pass DataProvider raw pointer (dataProvider.get()) to DataUserImpl. The object that you are pointing with the raw pointer will be destroyed when all shared_ptrs holding it will go.

    Since you do not save shared_ptr anywhere, with the end of it, your DataProvider object is destructed too and you are pointing to invalid memory.

    The solution would be to save shared_ptr as a field of DataUserImpl instead of the raw pointer. In this way, the lifetime of your object will be prolongated to the lifetime of DataUserImpl.