Delphi и COM: TLB и проблемы обслуживания

Основная проблема здесь в том, что DataTableConverter Json.NET записывает каждый DataColumn.DataType , просматривая значения токенов, присутствующие только в первой строке . Он работает таким образом, потому что он переводит JSON для таблицы, а не загружает целостность в промежуточную иерархию JToken . Хотя потоковая передача обеспечивает лучшую производительность при уменьшенном использовании памяти, это означает, что значения null в первой строке могут приводить к некорректно типизированным столбцам.

Это проблема, возникающая время от времени в stackoverflow, для экземпляр в вопросе deserialize datatable с отсутствующим первым столбцом . В этом случае вопросник заранее знал, что тип столбца должен быть double. В вашем случае вы указали, что схема datatable является динамической , поэтому ответ не может быть использован. Однако, как и в случае с этим вопросом, поскольку Json.NET является открытым исходным кодом под лицензией MIT , можно создать модифицированную версию своего DataTableConverter с необходимой логикой.

Как оказалось, можно корректно установить тип столбца, сохранив при этом поведение потоковой передачи, запомнив столбцы с неоднозначными типами данных, а затем заменив эти столбцы на правильно напечатанные столбцы, когда можно определить правильный тип:

/// 
/// Converts a  to and from JSON.
/// 
public class TypeInferringDataTableConverter : Newtonsoft.Json.Converters.DataTableConverter
{
    // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/DataTableConverter.cs
    // Original license: https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md

    /// 
    /// Reads the JSON representation of the object.
    /// 
    /// The  to read from.
    /// Type of the object.
    /// The existing value of object being read.
    /// The calling serializer.
    /// The object value.
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        DataTable dt = existingValue as DataTable;

        if (dt == null)
        {
            // handle typed datasets
            dt = (objectType == typeof(DataTable))
                ? new DataTable()
                : (DataTable)Activator.CreateInstance(objectType);
        }

        // DataTable is inside a DataSet
        // populate the name from the property name
        if (reader.TokenType == JsonToken.PropertyName)
        {
            dt.TableName = (string)reader.Value;

            reader.ReadAndAssert();

            if (reader.TokenType == JsonToken.Null)
            {
                return dt;
            }
        }

        if (reader.TokenType != JsonToken.StartArray)
        {
            throw JsonSerializationExceptionHelper.Create(reader, "Unexpected JSON token when reading DataTable. Expected StartArray, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
        }

        reader.ReadAndAssert();

        var ambiguousColumnTypes = new HashSet();

        while (reader.TokenType != JsonToken.EndArray)
        {
            CreateRow(reader, dt, serializer, ambiguousColumnTypes);

            reader.ReadAndAssert();
        }

        return dt;
    }

    private static void CreateRow(JsonReader reader, DataTable dt, JsonSerializer serializer, HashSet ambiguousColumnTypes)
    {
        DataRow dr = dt.NewRow();
        reader.ReadAndAssert();

        while (reader.TokenType == JsonToken.PropertyName)
        {
            string columnName = (string)reader.Value;

            reader.ReadAndAssert();

            DataColumn column = dt.Columns[columnName];
            if (column == null)
            {
                bool isAmbiguousType;

                Type columnType = GetColumnDataType(reader, out isAmbiguousType);
                column = new DataColumn(columnName, columnType);
                dt.Columns.Add(column);

                if (isAmbiguousType)
                    ambiguousColumnTypes.Add(columnName);
            }
            else if (ambiguousColumnTypes.Contains(columnName))
            {
                bool isAmbiguousType;
                Type newColumnType = GetColumnDataType(reader, out isAmbiguousType);
                if (!isAmbiguousType)
                    ambiguousColumnTypes.Remove(columnName);
                if (newColumnType != column.DataType)
                {
                    column = ReplaceColumn(dt, column, newColumnType, serializer);
                }
            }

            if (column.DataType == typeof(DataTable))
            {
                if (reader.TokenType == JsonToken.StartArray)
                {
                    reader.ReadAndAssert();
                }

                DataTable nestedDt = new DataTable();

                var nestedUnknownColumnTypes = new HashSet();

                while (reader.TokenType != JsonToken.EndArray)
                {
                    CreateRow(reader, nestedDt, serializer, nestedUnknownColumnTypes);

                    reader.ReadAndAssert();
                }

                dr[columnName] = nestedDt;
            }
            else if (column.DataType.IsArray && column.DataType != typeof(byte[]))
            {
                if (reader.TokenType == JsonToken.StartArray)
                {
                    reader.ReadAndAssert();
                }

                List o = new List();

                while (reader.TokenType != JsonToken.EndArray)
                {
                    o.Add(reader.Value);
                    reader.ReadAndAssert();
                }

                Array destinationArray = Array.CreateInstance(column.DataType.GetElementType(), o.Count);
                Array.Copy(o.ToArray(), destinationArray, o.Count);

                dr[columnName] = destinationArray;
            }
            else
            {
                object columnValue = (reader.Value != null)
                    ? serializer.Deserialize(reader, column.DataType) ?? DBNull.Value
                    : DBNull.Value;

                dr[columnName] = columnValue;
            }

            reader.ReadAndAssert();
        }

        dr.EndEdit();
        dt.Rows.Add(dr);
    }

    static object RemapValue(object oldValue, Type newType, JsonSerializer serializer)
    {
        if (oldValue == null)
            return null;
        if (oldValue == DBNull.Value)
            return oldValue;
        return JToken.FromObject(oldValue, serializer).ToObject(newType, serializer);
    }

    private static DataColumn ReplaceColumn(DataTable dt, DataColumn column, Type newColumnType, JsonSerializer serializer)
    {
        var newValues = Enumerable.Range(0, dt.Rows.Count).Select(i => dt.Rows[i]).Select(r => RemapValue(r[column], newColumnType, serializer)).ToList();

        var ordinal = column.Ordinal;
        var name = column.ColumnName;
        var @namespace = column.Namespace;

        var newColumn = new DataColumn(name, newColumnType);
        newColumn.Namespace = @namespace;
        dt.Columns.Remove(column);
        dt.Columns.Add(newColumn);
        newColumn.SetOrdinal(ordinal);

        for (int i = 0; i < dt.Rows.Count; i++)
            dt.Rows[i][newColumn] = newValues[i];

        return newColumn;
    }

    private static Type GetColumnDataType(JsonReader reader, out bool isAmbiguous)
    {
        JsonToken tokenType = reader.TokenType;

        switch (tokenType)
        {
            case JsonToken.Integer:
            case JsonToken.Boolean:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Date:
            case JsonToken.Bytes:
                isAmbiguous = false;
                return reader.ValueType;
            case JsonToken.Null:
            case JsonToken.Undefined:
                isAmbiguous = true;
                return typeof(string);
            case JsonToken.StartArray:
                reader.ReadAndAssert();
                if (reader.TokenType == JsonToken.StartObject)
                {
                    isAmbiguous = false;
                    return typeof(DataTable); // nested datatable
                }
                else
                {
                    isAmbiguous = false;
                    bool innerAmbiguous;
                    // Handling ambiguity in array entries is not yet implemented because the first non-ambiguous entry in the array
                    // might occur anywhere in the sequence, requiring us to scan the entire array to determine the type, 
                    // e.g., given: [null, null, null, 314, null]
                    // we would need to scan until the 314 value, and do:
                    // return typeof(Nullable<>).MakeGenericType(new[] { reader.ValueType }).MakeArrayType();
                    Type arrayType = GetColumnDataType(reader, out innerAmbiguous);
                    return arrayType.MakeArrayType();
                }
            default:
                throw JsonSerializationExceptionHelper.Create(reader, "Unexpected JSON token when reading DataTable: {0}".FormatWith(CultureInfo.InvariantCulture, tokenType));
        }
    }
}

internal static class JsonSerializationExceptionHelper
{
    public static JsonSerializationException Create(this JsonReader reader, string format, params object[] args)
    {
        // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPosition.cs

        var lineInfo = reader as IJsonLineInfo;
        var path = (reader == null ? null : reader.Path);
        var message = string.Format(CultureInfo.InvariantCulture, format, args);
        if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal))
        {
            message = message.Trim();
            if (!message.EndsWith(".", StringComparison.Ordinal))
                message += ".";
            message += " ";
        }
        message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
        if (lineInfo != null && lineInfo.HasLineInfo())
            message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
        message += ".";

        return new JsonSerializationException(message);
    }
}

internal static class StringUtils
{
    // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/StringUtils.cs
    public static string FormatWith(this string format, IFormatProvider provider, object arg0)
    {
        return format.FormatWith(provider, new[] { arg0 });
    }

    private static string FormatWith(this string format, IFormatProvider provider, params object[] args)
    {
        return string.Format(provider, format, args);
    }
}

internal static class JsonReaderExtensions
{
    public static void ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException("reader");
        if (!reader.Read())
        {
            throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading JSON.");
        }
    }
}

Затем используйте его как:

var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };

DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonTable1, settings);
DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonTable2, settings);

Не устанавливайте NullValueHandling = NullValueHandling.Ignore , поскольку нули теперь обрабатываются правильно.

Прототип fiddle

Обратите внимание, что, хотя этот класс обрабатывает повторную типизацию столбцов со значениями null, он не обрабатывает повторную типизацию столбцов, содержащих значения массива, где первый элемент массива имеет значение null. Например, если первая строка некоторого столбца имеет значение

[null, null, null, 314, null]

Тогда тип столбца, который был бы выбран, в идеале был бы typeof( long? [] ), однако это не выполняется здесь. Вероятно, необходимо было бы полностью загрузить JSON в иерархию JToken, чтобы сделать это определение.

9
задан Yeldar Kurmangaliyev 20 November 2015 в 11:40
поделиться

5 ответов

Я думаю, что у Вас должен быть хороший взгляд на Delphi 2009.

Delphi 2009 имеет изменения в поддержке COM, включая основанную на тексте замену для двоичных файлов TLB.

Можно читать больше на блоге Chris Bensen.

10
ответ дан 4 December 2019 в 07:49
поделиться

В удаленном прошлом (прежде чем я начал работать на CodeGear) я разочаровался в нечетном Delphi-ized язык IDL, что IDE, представленный, и, записал мой собственный IDL и скомпилировал его с помощью MS midl. Это в основном работало; единственная выгода, IIRC, удостоверялась, что dispids (идентификационный атрибут) были корректны в интерфейсах автоматизации (dispinterfaces) для методов считывания свойства и методов set - был некоторый инвариант, который ожидал tlibimp, но midl не гарантировал.

Однако теперь, когда Delphi 2009 использует безопасное подмножество midl синтаксиса и включает компилятор для этого midl в поле и интегрированный в IDE, этими проблемами должна быть вещь прошлого.

8
ответ дан 4 December 2019 в 07:49
поделиться

Мы также только что установили Delphi 2009, и это, действительно кажется, улучшило поддержку Typelibraries. Однако я работал с COM и библиотеками типов в течение достаточно долгого времени и здесь являюсь своими общими глюками, которые я нашел за эти годы. Я согласовал бы его симпатичный багги и являюсь полностью до Delphi 2006 (наша версия до использования 2009).

  • Всегда имейте каждый файл, записываемый перед открытием. Это может звучать очевидным, но при работе с управлением исходным кодом иногда мы забываем делать это и пытаться удалить флаг только для чтения после открытия файла - соглашение о наклоне Delphi с этим. Удостоверьтесь, что tlb перезаписываем перед открытием.
  • При редактировании автономного typelibrary у Вас ДОЛЖЕН быть открытый проект. По некоторым причинам при открытии библиотеки типов самостоятельно, она не сохранит. Создайте пустой проект и затем откройте свой typelibrary. По некоторым причинам это позволяет библиотеке типов быть сохраненной.
  • Если Ваша библиотека типов используется приложением, или COM + гарантируют, что приложение закрывается, или COM + отключен прежде, чем открыть библиотеку типов. Любые открытые приложения будут препятствовать тому, чтобы библиотека типов была сохранена.

Однако я думаю, что Вашим лучшим решением является, вероятно, обновление. Вы получаете поддержку Unicode также.

5
ответ дан 4 December 2019 в 07:49
поделиться

Используя Delphi 2009 значительно вынул большую часть боли из огромных файлов TLB, и преобразование наших существующих объектов было безболезненным, но наши объекты com не пользуются никакими сторонними библиотеками.

Мы будем перемещать наши gui приложения по тому, после того как поставщики библиотеки выпускают поддерживаемые версии.

3
ответ дан 4 December 2019 в 07:49
поделиться

Тот же опыт с интерфейсом TLB здесь: мы просто прекратили использовать его.

Мы работаем с несколькими отдельными файлами IDL (ручная сборка) для различных частей нашей платформы, используя конструкцию #include для включения их в IDL реального приложения, затем генерируем единственный tlb использование MIDL и tlibimp это. Если приложение не имеет никакого IDL предварительная скомпилированная версия сама по себе другой платформы, файлы TLB доступны.

Каждый раз, когда платформа вводит новую версию, скрипт запущен, чтобы повторно создать ГУИДЫ во всех необходимых интерфейсах в файлах IDL.

Это служило нам хорошо много лет, и чтобы мы отодвинулись, новый набор инструментов IDL/TLB Delphi 2009 должен будет быть не только интегрирован в IDE, но также и универсальный когда дело доходит до автоматизированных сборок и этажерки. Не могу дождаться, чтобы пачкать руки с некоторыми экспериментами!

2
ответ дан 4 December 2019 в 07:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: