DataTable.ReadXml не работает в удобной библиотеке при вызове из Word VBA
Я пытаюсь десериализовать строку XML в DataTable. Когда я вызываю код из.NET (проект WinForms), код работает отлично, но если я вызываю код из Word VBA, я получаю исключение, говорящее "Значение не может быть нулевым".
Просто чтобы прояснить, мой проект Winforms - это модуль администратора, а видимая библиотека COM - это мой клиент. Модуль администратора не имеет ссылки на мою библиотеку COM. Библиотека COM - это в основном оболочка для других моих классов.
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Data.Common.ObjectStorage.ConvertXmlToObject(XmlReader xmlReader, XmlRootAttribute xmlAttrib)
at System.Data.XmlDataLoader.LoadColumn(DataColumn column, Object[] foundColumns)
at System.Data.XmlDataLoader.LoadTable(DataTable table, Boolean isNested)
at System.Data.XmlDataLoader.LoadData(XmlReader reader)
at System.Data.DataTable.ReadXml(XmlReader reader, Boolean denyResolving)
at System.Data.DataTable.ReadXml(TextReader reader)
Следующий код - это то, что я использую для десериализации таблицы.
var table = new DataTable();
using (var stringReader = new StringReader(tableXmlString))
table.ReadXml(stringReader);
Моей первой мыслью было, конечно, что xml был неправильным, поэтому я попытался использовать жестко закодированную строку, сначала в моем проекте WinForms, чтобы я знал, что он работает, а затем в моей видимой библиотеке COM. Я сделал это, написав некоторый код для десериализации с жестко закодированной строкой, а затем вызвал этот метод из VBA, но он все еще не удался.
После многих испытаний я пришел к выводу, что это должно быть как-то связано с кодом, вызываемым из VBA, чтобы доказать, что я создал новый тестовый проект WinForms, добавил ссылку на мою COM-библиотеку и выполнил код таким же образом. я сделал из VBA. Как я и ожидал, исключений не было, и десериализация прошла нормально.
Последнее, что мне нужно, это то, что у меня есть пользовательский тип, который находится на входе xml.
Спекуляция начинается:
- Причина, по которой это работает в моем проекте WinForms, заключается в том, что DLL и тип "известны" сериализатору xml, но при вызове из VBA каким-то образом сериализатор не может найти / создать тип. примечание: тип уже используется в коде как в проекте WinForms, так и в библиотеке COM, поэтому он не является "неизвестным" ни одному из проектов.
- Каким-то образом при вызове из VBA сериализатор использует другую кодировку, которая путается с вводом xml.
Я решил не включать входные данные xml, так как уже установил, что они могут быть десериализованы. Кроме того, у меня есть только строка с выходом xml, затрудняющая "понимание".
Я надеюсь, что кто-то может помочь мне, потому что я очень расстроен этим. Если у вас есть какие-либо вопросы или вы чувствуете, что вам нужно больше информации, не стесняйтесь спрашивать.
1 ответ
Я так и не нашел фактическую причину, почему это происходит, но я нашел решение. Это не самое красивое решение, но оно работает, и, надеюсь, это сэкономит кому-то еще много времени.
Я предположил, что так или иначе тип не может быть найден, это почти правда. Фактическая проблема заключалась в том, что сборка, содержащая мой тип, не могла быть найдена. При тестировании непосредственно перед вызовом DataTable.ReadXml я мог видеть, что сборка фактически была загружена в AppDomain, но когда-то внутри библиотеки System.Data, это было уже не так, если я звонил из VBA.
Я подозреваю, что код внутри DataTable фактически выполнялся в контексте Word AppDomain, но я не уверен.
Решением всего этого является разрешение сборки вручную путем подключения к AppDomain.CurrentDomain.AssemblyResolve и использования следующего кода:
System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
var myType = typeof(MyType);
var assembly = myType.Assembly;
//Get the assembly name with comma. I do not care about version in this instance, but it is generally a good idea to include.
//ex: MyTypeAssembly,
var subString = assembly.FullName.Substring(0, assembly.FullName.IndexOf(',') + 1);
if (args.Name.StartsWith(subString))
return myType.Assembly;
}
catch
{
return null;
}
return null;
}
Я все еще заинтересован в объяснении, поэтому, если у кого-то есть такое, я весь слух:) Кроме того, решение не очень красивое, поэтому, если у кого-то есть лучшее предложение, поделитесь им.