如何使用SWIG

问题描述

我正在尝试为System.Text.Json.Utf8JsonWriter和System.Text.Json.Utf8JsonReader构建C ++包装器(使用SWIG),以使C ++代码可以将JSON读写到同一读者。客户端C#代码。为此,我要在C ++中创建与Utf8JsonWriter和Utf8JsonReader类的接口匹配的抽象类,然后使用SWIG Director功能使我能够在C#中创建派生类,该派生类实现此接口并充当调用相应接口的代理实际读取器/写入器上的方法。例如,在C ++中,JsonWriter类定义为:

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

    virtual void WriteStartObject() = 0;
    virtual void WriteStartObject(const char* propertyName) = 0;
    virtual void WriteEndobject() = 0;

    // .. etc
};

SWIG(启用了导演功能)会在C#中生成一个匹配的类,并通过将委托(回调)传递给它在C ++中自动生成的派生类来连接方法。这使我能够在C#中实现一个派生类,该类重写每个方法并充当实际Utf8JsonWriter的代理,如下所示:

public class JsonWriterProxy : JsonWriter
{
    private readonly Utf8JsonWriter _writer;

    public JsonWriterProxy(Utf8JsonWriter writer)
        : base()
    {
        _writer = writer;
    }

    public override void WriteStartObject() => _writer.WriteStartObject();
    public override void WriteStartObject(string propertyName) => _writer.WriteStartObject(propertyName);
    public override void WriteEndobject() => _writer.WriteEndobject();
    // ... etc
}

这对于Writer来说效果很好,而且考虑到所有幕后打来的电话,其性能令人惊讶地好。问题出在System.Text.Json.Utf8JsonReader实现上。在这种情况下,Microsoft选择将Utf8JsonReader实现为“引用结构”而不是类。这意味着我不能简单地在代理类中存储对原始Utf8JsonReader对象的引用(因为该语言禁止这样做)。我可以通过将Utf8JsonReader与代理对象一起传递给C ++反序列化方法解决此问题。 C ++反序列化方法如下:

 typedef void* RefJsonReader;
 void Deserialize(cylite::io::JsonReader* reader,RefJsonReader refReader)
 {
        reader->SetRefReader(refReader);
        if (reader->TokenType() != JsonTokenType::StartObject) throw new std::exception();
        while (reader->Read())
        {
        // .. etc
        }
}

以及相关的C#pinvoke定义:

[DllImport("IO",EntryPoint="CSharp_fIO_Exam_Deserialize___")]

public static extern void Deserialize(global::System.Runtime.InteropServices.HandleRef jarg1,global::System.Runtime.InteropServices.HandleRef jarg2,ref System.Text.Json.Utf8JsonReader jarg3);

C ++反序列化代码在C ++ JsonReader上调用SetRefReader方法以保存该引用,然后在每次调用时将其传递回C#委托,如下所示:

typedef void* RefJsonReader;

class JsonReader
{
private:
    
    RefJsonReader _refReader = nullptr;

public:

    virtual ~JsonReader() {}

    inline void SetRefReader(RefJsonReader reader)
    {
        _refReader = reader;
    }

    inline bool Read()
    {
        return Read(_refReader);
    }

    inline void Skip()
    {
        return Skip(_refReader);
    }
    // .. etc


protected:

    virtual bool Read(RefJsonReader reader) = 0;
    virtual void Skip(RefJsonReader reader) = 0;
    virtual unsigned char TokenType(RefJsonReader reader) = 0;
    // .. etc
} 

以及相应的C#Reader代理类(在每个方法中均采用ref Utf8JsonReader参数):

   public class JsonReaderProxy : JsonReader
    {
        protected override bool Read(ref Utf8JsonReader reader) => reader.Read();
        protected override void Skip(ref Utf8JsonReader reader) =>  reader.Skip();
        protected override byte TokenType(ref Utf8JsonReader reader) => (byte)reader.TokenType;
        // .. etc
    }

代码实际上有效。但是必须同时将代理类和ref Utf8JsonReader传递给每个反序列化方法(使它与编写不对称)有点混乱。我想做的是从代理类的构造函数中有效地调用SetRefReader方法,这意味着我不必将其作为附加参数传递给每个反序列化方法。但是,这不起作用(由于内存错误而崩溃)。起初我以为仅仅是因为通过ref传递时传递的实际上是本地堆栈变量的地址,而该地址又将地址保存到实际的struct中。因此,我更改了SetReader调用,以获取参数的地址,并将取消引用的地址保存在_refReader变量中,然后将该变量的地址传递回Proxy的调用中,如下所示:

inline void SetRefReader(RefJsonReader* reader)
{
     _refReader = *reader;
}

inline bool Read()
{
    return Read(&_refReader);
}

这看起来很有希望,因为我可以看到reader参数的值不同,这取决于在调用堆栈中从何处调用的,被取消引用的* reader值始终是相同的地址。但是,当通过带有ref Utf8JsonReader参数的委托方法回调到代理时,这也会崩溃。

对于C#如何传递ref struct参数,似乎有些我不完全了解。如果有人有见识,将不胜感激。对于这个问题的长度,我深表歉意,但我认为值得为我要实现的目标提供一些背景信息。

编辑: 一些进一步的信息。为了更好地理解这一点,我创建了自己的ref结构,如下所示:

public ref struct TestStruct
{
    public int value;
    public Span<byte> data;
}

我只更改了C#导入定义以通过它,而不是Utf8JsonReader引用结构。这完全没有问题,完全可以设置构造函数的引用。通过在结构中填充数据值,很明显,传递的实际上只是指向数据的指针(而不是指针的地址)。

奇怪的是,如果我随后更改结构定义以嵌入Utf8JsonReader,例如:

public ref struct TestStruct
{
    public int value;
    Utf8JsonReader reader;
    public Span<byte> data;
}

如果我从构造函数设置值,它将再次失败。如果我从Serialize函数内部设置引用,则它会工作(就像以前一样),尽管有趣的是由于某种原因,当重复调用回调时,它会慢很多(大约x20),那么如果Utf8JsonReader不是该结构的一部分。这一切都很奇怪。为什么要在引用中传递的结构中包含其他结构,却根本改变了行为-为何会使它变慢。

编辑2:好,所以我想我知道可能会发生什么。我猜测出于某种原因,当敲打Utf8JsonReader(或包含它的结构)时,运行时决定通过在调用的途中将结构的副本复制到缓冲区中来编组参数。地址并在出站时从该缓冲区复制回来。由于在调用第二个函数时此缓冲区超出范围,因此该地址不再有效。委托调用可能发生相同的事情-这就是为什么它们这么慢。如果是这种情况,问题仍然存在,为什么不为自己的引用结构执行此操作,并且是否有更改它的条件?

编辑3:好,现在我知道了为什么Utf8JsonReader的行为有所不同。它是不可复制的类型,根据https://docs.microsoft.com/en-us/dotnet/framework/interop/copying-and-pinning,这意味着它将被复制和编组,因为我的简单结构可被复制并因此被固定。我想我真正需要的只是某种以不透明的方式通过C ++传递托管引用的方法-因为C ++确实需要知道类型是什么。理想情况下,我只是将ref转换为指针(使用一些不安全的代码)。但是,似乎无法创建指向引用结构的指针。也许是另一个问题。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)