Есть сторонняя COM библиотека, в API которой используется тип DATE. Есть Interop Assembly для этой библиотеки, где DATE маршалится как тип DateTime.
Некоторые COM объекты имеют DATE свойство — это некий временной token, идентифицирующий объект. Эти DATE значения можно передавать другим методам для извлечения объектов из базы (и других манипуляций).
Проблема следующая: из-за различного внутреннего представления дат (DATE — double, DateTime — long), при маршалинге теряется точность передаваемых значений. Поскольку в COM библиотеке сравнение дат выполняется на точное совпадение (по сути сравниваются double), то необходимый объект найти не удается.
Пример:
COMDBConnectionClass db = new COMDBConnectionClass();
COMClass expected = db.CustomObjects[0];
DateTime moment = expected.MomentDate;
COMClass actual = db.GetObjectByMoment(moment); // error
То, что точность теряется, демонстрирует и этот пример.
DateTime now = DateTime.UtcNow; // .NET date/timedouble toOle = now.ToOADate(); // .NET -> OLE (as it seems what happens on interop)
DateTime fromOleDate = DateTime.FromOADate(toOle); // OLE -> .NET (as it seems what happens on interop)
TimeSpan diff = now - fromOleDate; // The difference is less than 1 millisecond.
Есть ли какие-либо workaround-ы, решающие эту проблему?
Здравствуйте, Nikolkos, Вы писали:
N>Есть сторонняя COM библиотека, в API которой используется тип DATE. Есть Interop Assembly для этой библиотеки, N>... N>Есть ли какие-либо workaround-ы, решающие эту проблему?
Если честно, то
double toOle = now.ToOADate(); // .NET -> OLE (as it seems what happens on interop)
это делать бесчеловечно...
double как тип имеет длину 64 бита, а сопроцессор использует
80 бит, что будет в оставшихся битах одному богу известно.
По большей части я сомневаюсь что маршалинг идет через
приведение типа, но тем не менее,
попробуй форматировать дату как строку и конвертировать
ее обратно в дату.... хотя это прыжки через голову.
Здравствуйте, Аноним, Вы писали:
А>Если честно, то А>double toOle = now.ToOADate(); // .NET -> OLE (as it seems what happens on interop)
А>это делать бесчеловечно... А>double как тип имеет длину 64 бита, а сопроцессор использует А>80 бит, что будет в оставшихся битах одному богу известно. А>По большей части я сомневаюсь что маршалинг идет через А>приведение типа, но тем не менее, А>попробуй форматировать дату как строку и конвертировать А>ее обратно в дату.... хотя это прыжки через голову.
Дело в том, что API COM библиотеки не изменить — в TLB указан тип DATE (как ты предлагаешь подсунуть туда строку?). В том, что примерно так выполняется маршалинг я убедился в отладчике (имея исходный код COM библиотеки и сравнивая значения передаваемых/получаемых параметров).
Можно убедится в этом также, переопределив один из COM интерфейсов и заменив DateTime на double.
[ComImport, Guid("...")]
public interface ICOMClass_FIX
{
double MomentDate {get;}
}
Т.к. double является blittable типом, то проблем не возникает: double параметр COM библиотека нормально обрабатывает, идентифицируя объект. Как решение, переопределение каждого интерфейса таким образом не подходит. Во-первых, COM API _очень_ большой; во-вторых, объекты типа DATE могут передаваться в вариантных массивах; ну а в-третьих, double в качестве DateTime — это не .NET юзабильно (т.н. unfriendly .NET API).
PS
Небольшое пояснение: решается задача создания .NET оберток для имеющегося COM API.
Здравствуйте, Nikolkos, Вы писали:
N>Есть сторонняя COM библиотека, в API которой используется тип DATE. Есть Interop Assembly для этой библиотеки, где DATE маршалится как тип DateTime.
А как был сделан этот самый "Interop Assembly для этой библиотеки"?
Help will always be given at Hogwarts to those who ask for it.
_FR>А как был сделан этот самый "Interop Assembly для этой библиотеки"?
Я сделал с помощью TlbImp.exe из SDK. COM библиотека "сторонняя" в том смысле, что существующий API менять нельзя (а так эта COM библиотека вполне даже "своя" .
N>PS N>Небольшое пояснение: решается задача создания .NET оберток для имеющегося COM API.
тут надо решать задачу о неиспользовании баженого компонента,
который неверно работает с double а именно с его сравнением.
Операция >> DateTime fromOleDate = DateTime.FromOADate(toOle); // OLE -> .NET (as it seems what happens on interop) >> TimeSpan diff = now — fromOleDate; // The difference is less than 1 millisecond.
На мой взгляд абсолютно неверна.
Если вы возьмете и разберете оба значения
на состовляющие и сравните все поля, (ГГ.ММ.ДД ЧЧ.ММ.СС.ммм) то получите
два абсолютно идентичных времени.
Если не знаете как это сделать, попробуйте в Excel он разложит вам оба числа
если итересно знать самому, могу дать коды.
Убедитесь сами.
Здравствуйте, Dwarffy, Вы писали:
D>Операция >>> DateTime fromOleDate = DateTime.FromOADate(toOle); // OLE -> .NET (as it seems what happens on interop) >>> TimeSpan diff = now — fromOleDate; // The difference is less than 1 millisecond. D>На мой взгляд абсолютно неверна.
На данный момент меня больше волнует не то, что операция верна или нет. А то, что идентичность значений не соблюдается, хотя по сути выполняются операции над одним и тем же значением DateTime.
D>Если вы возьмете и разберете оба значения
D>
D>на состовляющие и сравните все поля, (ГГ.ММ.ДД ЧЧ.ММ.СС.ммм) то получите D>два абсолютно идентичных времени.
Понятно, что double значения, полученные в .NET, будут одинаковыми (dVal == toOle). Проблема в том, что при передаче DATE значений COM -> .NET и обратно (без каких-то манипуляций со значениями) теряется точность/идентичность этих значений DATE. Они отличаются менее, чем на 1 милисекунду. Ессно, что в формате ГГ.ММ.ДД ЧЧ.ММ.СС.ммм они будут одинаковые, т.к. тут не учитыватся микросекунды.
Здравствуйте, Nikolkos, Вы писали:
N>Здравствуйте, Dwarffy, Вы писали:
D>>Операция >>>> DateTime fromOleDate = DateTime.FromOADate(toOle); // OLE -> .NET (as it seems what happens on interop) >>>> TimeSpan diff = now — fromOleDate; // The difference is less than 1 millisecond. D>>На мой взгляд абсолютно неверна. N>На данный момент меня больше волнует не то, что операция верна или нет. А то, что идентичность значений не соблюдается, хотя по сути выполняются операции над одним и тем же значением DateTime.
D>>Если вы возьмете и разберете оба значения
D>>
D>>на состовляющие и сравните все поля, (ГГ.ММ.ДД ЧЧ.ММ.СС.ммм) то получите D>>два абсолютно идентичных времени.
N>... , чем на 1 милисекунду. Ессно, что в формате ГГ.ММ.ДД ЧЧ.ММ.СС.ммм они будут одинаковые, т.к. тут не учитыватся микросекунды.
ммм — стоит за милисекунды, микросекундную точность, я вообще не уверен
Вы уточните про микросекунды, вообще это реально?
Здравствуйте, Dwarffy, Вы писали:
D>ммм — стоит за милисекунды, микросекундную точность, я вообще не уверен D>Вы уточните про микросекунды, вообще это реально?
Почему нет? DateTime имеет представление как long, значение которого доступно через свойство Ticks:
A single tick represents one hundred nanoseconds or one ten-millionth of a second. There are 10,000 ticks in a millisecond.
The value of this property represents the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001, which represents DateTime.MinValue.
FromOADate, ToOADate как раз оперируют с этим значением ticks (можно посмотреть в рефлекторе). Так что формально точность до 1 тика (100 наносекунд = 0,1 микросекунда) DateTime обеспечивать должен.
Здравствуйте, Nikolkos, Вы писали:
N>То, что точность теряется, демонстрирует и этот пример. N>
N>DateTime now = DateTime.UtcNow; // .NET date/time
N>double toOle = now.ToOADate(); // .NET -> OLE (as it seems what happens on interop)
N>DateTime fromOleDate = DateTime.FromOADate(toOle); // OLE -> .NET (as it seems what happens on interop)
N>TimeSpan diff = now - fromOleDate; // The difference is less than 1 millisecond.
N>
N>Есть ли какие-либо workaround-ы, решающие эту проблему?
Это недостаток арифметических операций с double, и вообще для чисел с плавающей точкой.
Например 2.8-0.8 != 2
Обычно лечится тем, что сравнение делается только с некоторым округлением.
Re[7]: COM Interop: DateTime & OLE DATE
От:
Аноним
Дата:
27.04.09 13:23
Оценка:
Здравствуйте, Nikolkos, Вы писали:
N>Здравствуйте, Dwarffy, Вы писали:
N>FromOADate, ToOADate как раз оперируют с этим значением ticks (можно посмотреть в рефлекторе). Так что формально точность до 1 тика (100 наносекунд = 0,1 микросекунда) DateTime обеспечивать должен.
Молодой человек, вы меня извините, что я вас так называю,
но вы пытались взглянуть на описание OLE Datetime и DateTime,
повнимательнее?
Или хотите что бы тут прямо вам сказали, что "точность fload & double"
не совместимы после преобразования.
Но где вы, скажите на милость, наблюдаете микро?
в Datetime а-ля .NET, но тогда скажите на милость
откуда .NET
Как говориться. Вот вам и ответ.А то что
в программе происходят округления, так это уже по стандарту.
Вы извините, хоть где-то должен быть порядок.
Если "порядок" с double можно назвать порядком.
Здравствуйте, GlebZ, Вы писали:
GZ>Это недостаток арифметических операций с double, и вообще для чисел с плавающей точкой. GZ>Например 2.8-0.8 != 2 GZ>Обычно лечится тем, что сравнение делается только с некоторым округлением.
Тут не поспоришь, COM библиотека по сути некорректно сравнивает double значения. Но в данном случае это считается допустимым, т.к. эти DATE значения не участвуют в вычислениях, а используются лишь как идентификаторы (что вполне корректно работает внутри COM). Но при выполнении COM Interop, эти временные метки "искажаются" маршаллером.
В общем, на данный момент мне видится такой выход: дополнение существующего COM API методами, ориентированными на .NET Interop.
N>>FromOADate, ToOADate как раз оперируют с этим значением ticks (можно посмотреть в рефлекторе). Так что формально точность до 1 тика (100 наносекунд = 0,1 микросекунда) DateTime обеспечивать должен.
А>но вы пытались взглянуть на описание OLE Datetime и DateTime, А>повнимательнее?
Что я пропустил?
А>Или хотите что бы тут прямо вам сказали, что "точность fload & double" А>не совместимы после преобразования.
Спасибо, я в курсе про ошибки округления и неточность преобразований. Мой вопрос был в другом: как это можно обойти при COM Interop значений DATE.
А>http://msdn.microsoft.com/en-us/library/ms187819.aspx А>Accuracy — Rounded to increments of .000, .003, or .007 seconds
А>Но где вы, скажите на милость, наблюдаете микро? А>в Datetime а-ля .NET, но тогда скажите на милость А>откуда .NET
Ссылка на T-SQL не в кассу (я где-то говорил про SQL?). Используется не SQL база, а некая "кастомная" экзотическая база (Hypertrieve, сомневаюсь, что знакомо название). Так вот там "микро" наблюдается по полной программе (by design как говорится).
Здравствуйте, Аноним, Вы писали:
А>А то что в программе происходят округления, так это уже по стандарту.
Дайте, пожалуйста, ссылочку на этот самый стандарт, касающийся маршалинга DATE при COM Interop? Здесь или тут об этом "округлении"/"потери_точности" ни слова.
Возможное объяснение — DateTime не предназначен для оперирования микросекундами (там и такого свойства нет, хотя есть те же самые тики). Тогда, мое мнение, подобное поведение маршалера по умолчанию следовало специально где-то обговорить (как и методы ToOADate, FromOADate).
DateTime.ToOADate Method
Return Value
Type: System.Double
A double-precision floating-point number that contains an OLE Automation date equivalent to the value of this instance.
DateTime.FromOADate Method
Return Value
Type: System.DateTime
A DateTime that represents the same date and time as d.