Перенос 32-разрядного управляемого кода в 64-разрядную версию
Microsoft Corporation
Обновлено в мае 2005 г.
Область применения:
Microsoft .NET
Microsoft .NET Framework 2.0
Сводка: Узнайте, что связано с переносом 32-разрядных управляемых приложений в 64-разрядную версию, о проблемах, которые могут повлиять на миграцию, а также о доступных средствах. (17 печатных страниц)
Содержимое
Введение
Управляемый код в 32-разрядной среде
Введите среду CLR для 64-разрядной среды.
Миграция и вызов платформы
Миграция и взаимодействие COM
Миграция и небезопасный код
Миграция и маршалинг
Миграция и сериализация
Сводка
Введение
В этом техническом документе рассматриваются:
- Сведения о переносе управляемых приложений с 32-разрядных на 64-разрядные
- Проблемы, которые могут повлиять на миграцию
- Какие инструменты доступны для помощи
Эта информация не предназначена для того, чтобы быть предписывающей; вместо этого он предназначен для ознакомления с различными областями, которые подвержены проблемам в процессе миграции на 64-разрядную версию. На этом этапе нет конкретной "поваренной книги" шагов, которые можно выполнить и обеспечить работу кода на 64-разрядной версии. Сведения, содержащиеся в этом техническом документе, ознакомят вас с различными проблемами и тем, что следует изучить.
Как вы скоро увидите, если управляемая сборка не является 100 % типобезопасного кода, вам потребуется просмотреть приложение и его зависимости, чтобы определить проблемы с миграцией на 64-разрядную версию. Многие из пунктов, о которые вы узнаете в следующих разделах, можно решить с помощью изменений программирования. В ряде случаев также потребуется выделить время на обновление кода, чтобы правильно выполняться в 32-разрядной и 64-разрядной средах, если вы хотите, чтобы он выполнялся в обеих средах.
Microsoft .NET — это набор программных технологий для подключения информации, людей, систем и устройств. С момента выпуска 1.0 в 2002 году организациям удалось развернуть . Решения на основе NET, встроенные внутри компании, независимые поставщики программного обеспечения (ISV) или некоторые сочетания. Существует несколько типов приложений .NET, которые накладывают ограничения 32-разрядной среды. Эти проблемы включают, помимо прочего, потребность в более реальной адресуемой памяти и потребность в повышении производительности с плавающей запятой. x64 и Itanium обеспечивают лучшую производительность для операций с плавающей запятой, чем в x86. Однако также возможно, что результаты, полученные в x64 или Itanium, будут отличаться от результатов, полученных в x86. 64-разрядная платформа призвана помочь в решении этих проблем.
С выпуском платформа .NET Framework версии 2.0 корпорация Майкрософт включает поддержку управляемого кода, выполняемого на 64-разрядных платформах x64 и Itanium.
Управляемый код — это просто "код", который предоставляет достаточно сведений, чтобы среда CLR (.NET) предоставляла набор основных служб, включая:
- Самостоятельное описание кода и данных с помощью метаданных
- Проверка стека
- Безопасность
- Сборка мусора
- JIT-компиляция
В дополнение к управляемому коду существует несколько других определений, которые важно понимать при изучении проблем миграции.
Управляемые данные — данные, которые выделяются в управляемой куче и собираются с помощью сборки мусора.
Сборка — единица развертывания, которая позволяет среде CLR полностью понимать содержимое приложения и применять правила управления версиями и зависимостей, определенные приложением.
Типобезопасный код — код, использующий только управляемые данные и не допускающий проверки типы данных или неподдерживаемые операции преобразования и приведения типов данных (т. е. неразличимые объединения или указатели структуры или интерфейса). Код C#, Visual Basic .NET и Visual C++, скомпилированный с помощью /clr:safe , создает типобезопасный код.
Небезопасный код — код, которому разрешено выполнять такие операции более низкого уровня, как объявление указателей и работа с ними, выполнение преобразований между указателями и целочисленными типами, а также получение адреса переменных. Такие операции позволяют взаимодействовать с базовой операционной системой, получать доступ к сопоставленным в памяти устройствам или реализовывать алгоритм, критически важный по времени. Машинный код небезопасн.
Управляемый код в 32-разрядной среде
Чтобы понять сложности, связанные с переносом управляемого кода в 64-разрядную среду, давайте рассмотрим, как управляемый код выполняется в 32-разрядной среде.
Когда для выполнения выбрано управляемое или неуправляемое приложение, вызывается загрузчик Windows, который отвечает за принятие решения о загрузке и последующем выполнении приложения. Часть этого процесса включает в себя заголовок переносимого выполнения (PE) исполняемого файла, чтобы определить, требуется ли среда CLR. Как вы уже догадались, в pe есть флаги, указывающие на управляемый код. В этом случае загрузчик Windows запускает среду CLR, которая затем отвечает за загрузку и выполнение управляемого приложения. (Это упрощенное описание процесса, так как требуется выполнить множество шагов, включая определение версии среды CLR для выполнения, настройку домена Приложения "песочница" и т. д.)
Совместимость
При запуске управляемого приложения оно может (при условии соответствующих разрешений безопасности) взаимодействовать с собственными API (включая API Win32) и COM-объектами с помощью возможностей взаимодействия со средой CLR. Независимо от того, вызываете ли собственный API платформы, выполняете COM-запрос или маршалируете структуру, при выполнении полностью в 32-разрядной среде разработчик изолирован от необходимости думать о размерах типов данных и выравнивании данных.
При рассмотрении миграции на 64-разрядную версию важно изучить зависимости вашего приложения.
Введите среду CLR для 64-разрядной среды.
Чтобы управляемый код выполнялся в 64-разрядной среде в соответствии с 32-разрядной средой, команда .NET разработала среду CLR для 64-разрядных систем Itanium и x64. Среда CLR должна строго соответствовать правилам ОБЩЕЯзыковой инфраструктуры (CLI) и системы типов common Language Type System, чтобы обеспечить возможность взаимодействия кода, написанного на любом из языков .NET, так же, как и в 32-разрядной среде. Кроме того, ниже приведен список других компонентов, которые также пришлось перенести и (или) разработать для 64-разрядной среды:
- Библиотеки базовых классов (System.*)
- JIT-компилятор
- Поддержка отладки
- Пакет SDK для .NET Framework
Поддержка 64-разрядного управляемого кода
Платформа .NET Framework версии 2.0 поддерживает 64-разрядные процессоры Itanium и x64 под управлением:
- Windows Server 2003 с пакетом обновления 1 (SP1)
- Будущие выпуски 64-разрядных клиентов Windows
(Вы не можете установить платформа .NET Framework версии 2.0 в Windows 2000. Выходные файлы, созданные с помощью платформа .NET Framework версий 1.0 и 1.1, будут выполняться в WOW64 в 64-разрядной операционной системе.)
При установке платформа .NET Framework версии 2.0 на 64-разрядной платформе вы не только устанавливаете всю необходимую инфраструктуру для выполнения управляемого кода в 64-разрядном режиме, но и устанавливаете необходимую инфраструктуру для запуска управляемого кода в подсистеме Windows-on-Windows или WoW64 (32-разрядный режим).
Простая 64-разрядная миграция
Рассмотрим приложение .NET, которое является 100 % безопасным кодом. В этом сценарии можно взять исполняемый файл .NET, который вы запускаете на 32-разрядном компьютере, и переместить его в 64-разрядную систему и запустить его успешно. Почему это работает? Так как сборка является 100 % типобезопасной, мы знаем, что нет зависимостей от машинного кода или COM-объектов и что нет "небезопасного" кода, что означает, что приложение работает полностью под управлением среды CLR. Среда CLR гарантирует, что, хотя двоичный код, созданный в результате JIT-компиляции, будет отличаться от 32-разрядной и 64-разрядной версий, выполняемый код будет семантически одинаковым. (Вы не можете установить платформа .NET Framework версии 2.0 в Windows 2000. Выходные файлы, созданные с использованием платформа .NET Framework версий 1.0 и 1.1, будут работать в WOW64 в 64-разрядной операционной системе.)
На самом деле предыдущий сценарий немного сложнее с точки зрения загрузки управляемого приложения. Как упоминалось в предыдущем разделе, загрузчик Windows отвечает за выбор способа загрузки и выполнения приложения. Однако, в отличие от 32-разрядной среды, работа на 64-разрядной платформе Windows означает, что существует две (2) среды, в которых приложение может быть выполнено: в собственном 64-разрядном режиме или в WoW64.
Теперь загрузчик Windows должен принимать решения на основе того, что он обнаруживает в заголовке PE. Как вы могли догадаться, в управляемом коде есть устанавливаемые флаги, которые помогают в этом процессе. (Чтобы отобразить параметры в PE, см. corflags.exe.) В следующем списке представлены сведения, которые находятся в pe, которые помогают в процессе принятия решений.
- 64-разрядная — означает, что разработчик создал сборку специально для 64-разрядного процесса.
- 32-разрядная — означает, что разработчик создал сборку специально для 32-разрядного процесса. В этом случае сборка будет выполняться в WoW64.
- Agnostic — означает, что разработчик создал сборку с помощью Visual Studio 2005 с кодовыми именами Whidbey. или более поздних версий, а сборка может выполняться в 64-разрядном или 32-разрядном режиме. В этом случае 64-разрядный загрузчик Windows будет запускать сборку в 64-разрядной версии.
- Устаревшая версия — означает, что средства, которые создали сборку, были "pre-Whidbey". В этом конкретном случае сборка будет выполняться в WoW64.
Примечание В pe также есть информация, которая сообщает загрузчику Windows, является ли сборка целевой для конкретной архитектуры. Эти дополнительные сведения гарантируют, что сборки, предназначенные для определенной архитектуры, не загружаются в другую.
Компиляторы C#, Visual Basic .NET и C++ Whidbey позволяют задать соответствующие флаги в заголовке PE. Например, C# и THIRD имеют параметр компилятора /platform:{anycpu, x86, Itanium, x64} .
Примечание Хотя технически можно изменить флаги в заголовке PE сборки после ее компиляции, корпорация Майкрософт не рекомендует это делать.
Если вам интересно узнать, как эти флаги устанавливаются в управляемой сборке, можно запустить служебную программу ILDASM, предоставляемую в пакете SDK для платформа .NET Framework. На следующем рисунке показано устаревшее приложение.
Помните, что разработчик, пометив сборку как Win64, определил, что все зависимости приложения будут выполняться в 64-разрядном режиме. 64-разрядный процесс не может использовать 32-разрядный компонент в процессе (а 32-разрядный процесс не может загрузить 64-разрядный компонент в процессе). Имейте в виду, что возможность загрузки сборки системой в 64-разрядный процесс автоматически не означает, что она будет выполняться правильно.
Итак, теперь мы знаем, что приложение, состоящее из управляемого кода, который является 100 % типобезопасным, можно скопировать (или развернуть xcopy ) на 64-разрядную платформу и обеспечить JIT-код и успешное выполнение с .NET в 64-разрядном режиме.
Тем не менее, мы часто видим ситуации, которые не являются идеальными, и это подводит нас к main основное внимание в этом документе, который заключается в повышении осведомленности о проблемах, связанных с миграцией.
У вас может быть приложение, которое не является 100 % безопасным и по-прежнему может успешно работать в 64-разрядной версии в .NET. Для вас будет важно внимательно взглянуть на приложение, учитывая потенциальные проблемы, рассмотренные в следующих разделах, и определить, можно ли успешно выполнить 64-разрядную версию.
Миграция и вызов платформы
Использование возможностей вызова платформы (или p/invoke) в .NET относится к управляемому коду, который выполняет вызовы неуправляемого или машинного кода. В типичном сценарии машинный код представляет собой библиотеку динамической компоновки (DLL), которая является частью системы (Windows API и т. д.), частью приложения или сторонней библиотекой.
Использование неуправляемого кода не означает явным образом, что миграция на 64-разрядную версию будет иметь проблемы; вместо этого следует считать индикатором того, что требуется дополнительное исследование.
Типы данных в Windows
Каждое приложение и каждая операционная система имеют абстрактную модель данных. Многие приложения не предоставляют эту модель данных явным образом, но она определяет способ написания кода приложения. В 32-разрядной модели программирования (известной как модель ILP32) типы данных целочисленного, длинного и указателя имеют длину 32 бита. Большинство разработчиков использовали эту модель, не осознавая этого.
В 64-разрядной версии Microsoft Windows такое предположение о четности размеров типов данных недопустимо. Создание всех типов данных длиной 64 бита приведет к трате места, так как большинству приложений не требуется увеличенный размер. Однако приложениям требуются указатели на 64-разрядные данные, а также возможность иметь 64-разрядные типы данных в выбранных случаях. Эти рекомендации побудило команду Windows выбрать абстрактную модель данных ПОД названием LLP64 (или P64). В модели данных LLP64 только указатели расширяются до 64 бит; все остальные базовые типы данных (целочисленные и длинные) остаются длиной 32 бита.
Среда CLR .NET для 64-разрядных платформ использует ту же абстрактную модель данных LLP64. В .NET существует целочисленный тип данных, не широко известный, специально предназначенный для хранения сведений о указателе: IntPtr , размер которого зависит от платформы (например, 32-разрядной или 64-разрядной), на которой она выполняется. Рассмотрим следующий фрагмент кода:
[C#]
public void SizeOfIntPtr() {
Console.WriteLine( "SizeOf IntPtr is: {0}", IntPtr.Size );
}
При запуске на 32-разрядной платформе вы получите следующие выходные данные в консоли:
SizeOf IntPtr is: 4
На 64-разрядной платформе вы получите следующие выходные данные на консоли:
SizeOf IntPtr is: 8
Примечание Если вы хотите проверка во время выполнения, работаете ли вы в 64-разрядной среде, можно использовать IntPtr.Size в качестве одного из способов определения.
Вопросы миграции
При переносе управляемых приложений, использующих p/invoke, учитывайте следующие моменты:
- Доступность 64-разрядной версии библиотеки DLL
- Использование типов данных
Доступность
В первую очередь необходимо определить, доступен ли неуправляемый код, от которого зависит приложение, для 64-разрядной версии.
Если этот код был разработан внутри организации, ваши способности к успеху увеличиваются. Конечно, вам по-прежнему потребуется выделить ресурсы для переноса неуправляемого кода в 64-разрядную версию вместе с соответствующими ресурсами для тестирования, контроля качества и т. д. (В этом техническом документе не содержатся рекомендации по процессам разработки. Скорее он пытается указать на то, что ресурсы, возможно, потребуется выделить задачи для переноса кода.)
Если этот код получен от стороннего производителя, необходимо выяснить, есть ли у этой третьей стороны уже доступный код для 64-разрядной версии и будет ли третья сторона готова сделать его доступным.
Проблема с более высоким риском возникает, если третья сторона больше не предоставляет поддержку этого кода или если третья сторона не хочет выполнять эту работу. В этих случаях требуется дополнительное исследование доступных библиотек, которые выполняют аналогичные функции, позволит ли третья сторона клиенту делать порт самостоятельно и т. д.
Важно помнить, что 64-разрядная версия зависимого кода может иметь измененные сигнатуры интерфейса, что может означать дополнительные работы по разработке и устранить различия между 32-разрядными и 64-разрядными версиями приложения.
Типы данных
Для использования p/invoke требуется, чтобы код, разработанный в .NET, объявил прототип метода, предназначенного для управляемого кода. Учитывая следующее объявление C:
[C++]
typedef void * HANDLE
HANDLE GetData();
Ниже приведены примеры прототипированных методов.
[C#]
[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
public static extern int DoWork( int x, int y );
[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
public unsafe static extern int GetData();
Давайте рассмотрим эти примеры с прицелом на проблемы 64-разрядной миграции:
В первом примере вызывается метод DoWork , передавая два (2) 32-разрядных целых числа, и мы ожидаем, что будет возвращено 32-разрядное целое число. Несмотря на то, что мы работаем на 64-разрядной платформе, целое число по-прежнему составляет 32 бита. В этом конкретном примере нет ничего, что должно помешать нашим усилиям по миграции.
Второй пример требует внесения некоторых изменений в код для успешного выполнения в 64-разрядной версии. Здесь мы вызываем метод GetData и объявляем, что ожидается возврат целого числа, но функция фактически возвращает указатель int. В этом заключается наша проблема: помните, что целые числа имеют значение 32 бита, а в 64-разрядных указателях — 8 байт. Как оказалось, довольно много кода в 32-разрядном мире был написан при условии, что указатель и целое число были одинаковой длины, 4 байта. В 64-разрядном мире это больше не так.
В последнем случае проблему можно решить, изменив объявление метода, чтобы вместо int использовался IntPtr.
public unsafe static extern IntPtr GetData();
Это изменение будет работать как в 32-разрядной, так и в 64-разрядной средах. Помните, что IntPtr зависит от платформы.
Использование p/invoke в управляемом приложении не означает, что миграция на 64-разрядную платформу будет невозможна. Это также не означает, что будут проблемы. Это означает, что необходимо проверить зависимости от неуправляемого кода, который имеется в управляемом приложении, и определить, будут ли проблемы.
Миграция и взаимодействие COM
Взаимодействие COM — это предполагаемая возможность платформы .NET. Как и в предыдущем обсуждении вызова платформы, использование com-взаимодействия означает, что управляемый код вызывает неуправляемый код. Однако, в отличие от вызова платформы, взаимодействие COM также означает возможность неуправляемого кода вызывать управляемый код, как если бы он был com-компонентом.
Еще раз, использование неуправляемого COM-кода не означает, что миграция на 64-разрядную версию будет иметь проблемы. вместо этого следует считать индикатором того, что требуется дополнительное исследование.
Вопросы миграции
Важно понимать, что с выпуском платформа .NET Framework версии 2.0 не поддерживается взаимодействие между архитектурами. Чтобы быть более кратким, вы не можете использовать com-взаимодействие между 32-разрядными и 64-разрядными в одном процессе. Но вы можете использовать com-взаимодействие между 32-разрядными и 64-разрядными, если у вас есть внепроцессный COM-сервер. Если вы не можете использовать внепроцессный COM-сервер, необходимо пометить управляемую сборку как Win32, а не Win64 или Agnostic, чтобы программа выполнялась в WoW64, чтобы она смогла взаимодействовать с 32-разрядным COM-объектом.
Ниже рассматриваются различные аспекты, которые необходимо учитывать при использовании com-взаимодействия, когда управляемый код выполняет com-вызовы в 64-разрядной среде. В частности:
- Доступность 64-разрядной версии библиотеки DLL
- Использование типов данных
- Библиотеки типов
Доступность
Обсуждение в разделе p/invoke о доступности 64-разрядной версии зависимого кода также относится к этому разделу.
Типы данных
Обсуждение типов данных 64-разрядной версии зависимого кода в разделе p/invoke также относится к этому разделу.
Библиотеки типов
В отличие от сборок, библиотеки типов не могут быть помечены как нейтральные; Они должны быть помечены как Win32 или Win64. Кроме того, библиотека типов должна быть зарегистрирована для каждой среды, в которой будет выполняться COM. Используйте tlbimp.exe для создания 32-разрядной или 64-разрядной сборки из библиотеки типов.
Использование com-взаимодействия в управляемом приложении не означает, что миграция на 64-разрядную платформу будет невозможна. Это также не означает, что будут проблемы. Это означает, что необходимо проверить зависимости управляемого приложения и определить, будут ли проблемы.
Миграция и небезопасный код
Основной язык C# значительно отличается от C и C++ тем, что в качестве типа данных отсутствуют указатели. Вместо этого C# предоставляет ссылки и возможность создавать объекты, управляемые сборщиком мусора. В основном языке C# просто невозможно иметь неинициализированную переменную, "висячий" указатель или выражение, которое индексирует массив за его границы. Таким образом, устранены целые категории ошибок, которые регулярно преследуют программы C и C++.
Хотя практически каждая конструкция типа указателя в C или C++ имеет аналог ссылочного типа в C#, существуют ситуации, когда доступ к типам указателей становится необходимостью. Например, взаимодействие с базовой операционной системой, доступ к устройству, сопоставленное с памятью, или реализация алгоритма, критичного по времени, может быть невозможно или практически невозможно без доступа к указателям. Для решения этой проблемы C# предоставляет возможность писать небезопасный код.
В небезопасном коде можно объявлять указатели и работать с ними, выполнять преобразования между указателями и целочисленными типами, принимать адрес переменных и т. д. В каком-то смысле написание небезопасного кода во многом похоже на написание кода C в программе C#.
Небезопасный код на самом деле является "безопасной" функцией как для разработчиков, так и для пользователей. Небезопасный код должен быть четко помечен модификатором unsafe, чтобы разработчики не могли случайно использовать небезопасные функции.
Вопросы миграции
Чтобы обсудить потенциальные проблемы с небезопасным кодом, рассмотрим следующий пример. Наш управляемый код выполняет вызовы неуправляемой библиотеки DLL. В частности, существует метод GetDataBuffer , который возвращает 100 элементов (в этом примере мы возвращаем фиксированное количество элементов). Каждый из этих элементов состоит из целого числа и указателя. Приведенный ниже пример кода представляет собой фрагмент управляемого кода, показывающий небезопасную функцию, отвечающую за обработку этих возвращаемых данных.
[C#]
public unsafe int UnsafeFn() {
IntPtr * inputBuffer = sampleDLL.GetDataBuffer();
IntPtr * ptr = inputBuffer;
int result = 0;
for ( int idx = 0; idx < 100; idx ++ ) {
// Add 'int' from DLL to our result
result = result + ((int) *ptr);
// Increment pointer over int (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );
// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );
}
return result;
}
Примечание Этот конкретный пример можно было бы выполнить без использования небезопасного кода. В частности, существуют и другие методы, такие как маршалинг, которые можно было бы использовать. Но для этой цели мы используем небезопасный код.
UnsafeFn выполняет циклическое перебор 100 элементов и суммирует целочисленные данные. По мере того как мы просматриваем буфер данных, код должен перешаговливать как целое число, так и указатель. В 32-разрядной среде этот код работает нормально. Однако, как мы уже говорили ранее, указатели в 64-разрядной среде имеют размер 8 байтов, поэтому сегмент кода (показан ниже) не будет работать правильно, так как используется распространенный метод программирования, например, рассматривая указатель как эквивалент целочисленного.
// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );
Чтобы этот код работал как в 32-разрядной, так и в 64-разрядной среде, необходимо изменить код следующим образом.
// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( IntPtr ) );
Как мы только что видели, существуют экземпляры, в которых необходимо использовать небезопасный код. В большинстве случаев это необходимо из-за зависимости управляемого кода от какого-то другого интерфейса. Независимо от причин, по которым существует небезопасный код, он должен быть проверен в процессе миграции.
Приведенный выше пример относительно прост, и исправление для работы программы в 64-разрядной версии было простым. Очевидно, что есть много примеров небезопасного кода, которые являются более сложными. Некоторые из которых требуют глубокого анализа и, возможно, отката назад и переосмыслить подход, который использует управляемый код.
Повторение уже прочитанного — использование небезопасного кода в управляемом приложении не означает, что миграция на 64-разрядную платформу будет невозможна. Это также не означает, что будут проблемы. Это означает, что необходимо просмотреть весь небезопасный код управляемого приложения и определить, будут ли проблемы.
Миграция и маршалинг
Маршалинг предоставляет набор методов для выделения неуправляемой памяти, копирования неуправляемых блоков памяти и преобразования управляемых в неуправляемые типы, а также других методов, используемых при взаимодействии с неуправляемым кодом.
Маршалинг проявляется с помощью класса Маршал .NET. Статические или общие в Visual Basic методы, определенные в классе Marshal, необходимы для работы с неуправляемыми данными. Опытные разработчики, создающие пользовательские маршалеры, которым необходимо обеспечить мост между управляемыми и неуправляемыми моделями программирования, обычно используют большинство определенных методов.
Вопросы миграции
Маршалинг создает некоторые из более сложных проблем, связанных с переносом приложений на 64-разрядную версию. Учитывая характер того, что разработчик пытается выполнить с помощью маршалинга, а именно передачи структурированной информации в управляемый и неуправляемый код, а также из него, мы увидим, что мы предоставляем информацию, иногда низкоуровневую, для оказания помощи системе.
С точки зрения макета есть два конкретных объявления, которые могут быть сделаны разработчиком. Эти объявления обычно создаются с помощью атрибутов программирования.
LayoutKind.Sequential
Давайте рассмотрим определение, указанное в справке по пакету SDK для платформа .NET Framework:
"Элементы объекта размещаются последовательно в том порядке, в котором они отображаются при экспорте в неуправляемую память. Члены располагаются в соответствии с упаковкой, указанной в StructLayoutAttribute.Pack, и могут быть несмежными".
Нам говорят, что макет зависит от порядка, в котором он определен. Затем все, что нам нужно сделать, это убедиться, что управляемые и неуправляемые объявления похожи. Но, нам также говорят, что упаковка является критически важным ингредиентом, тоже. На этом этапе вы не будете удивлены, узнав, что без явного вмешательства разработчика существует значение пакета по умолчанию. Как вы уже могли догадаться, значение пакета по умолчанию не совпадает между 32-разрядными и 64-разрядными системами.
Утверждение в определении о несмежных членах ссылается на тот факт, что из-за размеров пакетов по умолчанию данные, которые размещаются в памяти, могут не иметь значения байтов 0, байтов 1, байт2 и т. д. Вместо этого первый элемент будет иметь значение байт 0, а второй элемент может иметь значение байт 4. Система выполняет эту упаковку по умолчанию, чтобы позволить машине получить доступ к членам без проблем с несоответствием.
Вот область, в которую мы должны обратить пристальное внимание на упаковку, и в то же время, попытаться позволить системе действовать в предпочтительном режиме.
Ниже приведен пример структуры, определенной в управляемом коде, а также соответствующей структуры, определенной в неуправляемом коде. Обратите внимание на то, как в этом примере показано задание значения пакета в обеих средах.
[C#]
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class XYZ {
public byte arraysize = unchecked((byte)-1);
[MarshalAs(UnmanagedType.ByValArray, SizeConst=52)]
public int[] padding = new int[13];
};
[unmanaged c++]
#pragma pack(1)
typedef struct{
BYTE arraysize; // = (byte)-1;
int padding[13];
} XYZ;
LayoutKind.Explicit
Давайте рассмотрим определение, указанное в справке .NET FrameworkSDK:
"Точное положение каждого элемента объекта в неуправляемой памяти контролируется явным образом. Каждый член должен использовать атрибут FieldOffsetAttribute , чтобы указать положение этого поля в типе.
Здесь нам говорят, что разработчик будет предоставлять точные смещения, чтобы помочь в маршале информации. Поэтому важно, чтобы разработчик правильно указал сведения в атрибуте FieldOffset .
Итак, где потенциальные проблемы? Учитывая, что смещения полей определяются с учетом размера члена данных, важно помнить, что не все размеры типов данных равны между 32-разрядными и 64-разрядными. В частности, длина указателей составляет 4 или 8 байт.
Теперь у нас есть случай, когда может потребоваться обновить управляемый исходный код для конкретных сред. В приведенном ниже примере показана структура, содержащая указатель. Несмотря на то, что мы сделали указатель intPtr, при переходе на 64-разрядную версию по-прежнему существует разница.
[C#]
[StructLayout(LayoutKind.Explicit)]
internal struct FooValue {
[FieldOffset(0)] public int dwType;
[FieldOffset(4)] public IntPtr pType;
[FieldOffset(8)] public int typeValue;
}
Для 64-разрядных мы должны настроить смещение поля для последнего элемента данных в структуре, так как оно действительно начинается со смещения 12, а не 8.
[C#]
[StructLayout(LayoutKind.Explicit)]
internal struct FooValue {
[FieldOffset(0)] public int dwType;
[FieldOffset(4)] public IntPtr pType;
[FieldOffset(12)] public int typeValue;
}
Использование маршалинга является реальностью, когда требуется сложное взаимодействие между управляемым и неуправляемным кодом. Использование этой мощной возможности не является показателем того, что вы можете перенести 32-разрядное приложение в 64-разрядную среду. Однако из-за сложностей, связанных с использованием маршалинга, это область, где требуется тщательное внимание к деталям.
Анализ кода покажет, требуются ли отдельные двоичные файлы для каждой из платформ и придется ли также вносить изменения в неуправляемый код для решения таких проблем, как упаковка.
Миграция и сериализация
Сериализация представляет собой процесс преобразования состояния объекта в форму, пригодную для сохранения или передачи. Дополнением к сериализации служит десериализация, при которой осуществляется преобразование потока в объект. Вместе они обеспечивают простое хранение и передачу данных.
Платформа .NET Framework поддерживает две технологии сериализации:
- При двоичной сериализации сохраняется правильность типов, что полезно для сохранения состояния объекта между разными вызовами приложения. Например, можно обеспечить совместный доступ к объекту для разных приложений, сериализовав его в буфер обмена. Объект можно сериализовать в поток, на диск, в память, передать по сети и т. д. Удаленное взаимодействие .NET использует сериализацию для передачи объектов "по значению" из одного компьютера или домена приложения в другой.
- При XML-сериализации сериализуются только открытые свойства и поля, а правильность типов не сохраняется. Этот метод полезен для предоставления или использования данных без ограничений работающего с ними приложения. Будучи открытым стандартом, XML привлекателен для совместного использования данных в Интернете. Аналогичным образом и SOAP представляет собой открытый стандарт, использование которого эффективно и удобно.
Вопросы миграции
Когда мы думаем о сериализации, мы должны иметь в виду, чего мы пытаемся достичь. При переходе на 64-разрядную версию следует помнить о том, планируете ли вы совместно использовать сериализованную информацию на разных платформах. Другими словами, 64-разрядное управляемое приложение будет считывать (или десериализировать) сведения, хранящиеся в 32-разрядном управляемом приложении.
Ваш ответ поможет повысить сложность решения.
- Возможно, вам потребуется написать собственные подпрограммы сериализации с учетом платформ.
- Вы можете ограничить общий доступ к информации, при этом разрешив каждой платформе считывать и записывать собственные данные.
- Вы можете вернуться к сериализации и внести изменения, чтобы избежать некоторых проблем.
Итак, после всего этого, каковы соображения в отношении сериализации?
- Длина IntPtr составляет 4 или 8 байт в зависимости от платформы. Если вы сериализуете данные, то записываете данные для конкретной платформы в выходные данные. Это означает, что при попытке поделиться этой информацией могут и будут возникать проблемы.
Если вы рассматриваете наше обсуждение в предыдущем разделе о маршале и смещениях, вы можете задать или два вопроса о том, как сериализация решает упаковочную информацию. Для двоичной сериализации .NET внутренне использует правильный неровновый доступ к потоку сериализации с помощью операций чтения на основе байтов и правильной обработки данных.
Как мы только что видели, использование сериализации не препятствует переходу на 64-разрядную версию. При использовании XML-сериализации необходимо преобразовать в собственные управляемые типы и в процессе сериализации, что изолирует вас от различий между платформами. Использование двоичной сериализации предоставляет более широкие возможности решения, но создает ситуацию, когда необходимо принимать решения о том, как различные платформы совместно используют сериализованную информацию.
Сводка
Ожидается переход на 64-разрядную версию, и корпорация Майкрософт работает над тем, чтобы сделать переход с 32-разрядных управляемых приложений на 64-разрядные как можно проще.
Однако нереально предположить, что можно просто выполнить 32-разрядный код в 64-разрядной среде и запустить его, не глядя на то, что вы переносите.
Как упоминалось ранее, если у вас есть 100 % безопасный управляемый код, вы можете просто скопировать его на 64-разрядную платформу и успешно запустить в 64-разрядной среде CLR.
Однако, скорее всего, управляемое приложение будет задействовано со следующими:
- Вызов API платформы через p/invoke
- Вызов COM-объектов
- Использование небезопасного кода
- Использование маршалинга в качестве механизма совместного использования информации
- Использование сериализации в качестве способа сохранения состояния
Независимо от того, какие из этих действий выполняет приложение, важно выполнить домашнее задание и изучить, что делает ваш код и какие зависимости у вас есть. После выполнения этого домашнего задания вам придется взглянуть на свой выбор, чтобы выполнить любое или все из следующих действий:
- Перенос кода без изменений.
- Внесите изменения в код для правильной обработки 64-разрядных указателей.
- Работайте с другими поставщиками и т. д., чтобы предоставить 64-разрядные версии своих продуктов.
- Внесите изменения в логику для обработки маршалинга и (или) сериализации.
Бывают случаи, когда вы решаете не переносить управляемый код на 64-разрядную версию. В этом случае вы можете пометить сборки, чтобы загрузчик Windows мог правильно выполнять действия при запуске. Помните, что подчиненные зависимости оказывают непосредственное влияние на общее приложение.
Fxcop
Кроме того, следует помнить о средствах, которые помогут вам выполнить миграцию.
Сегодня у корпорации Майкрософт есть средство FxCop, которое представляет собой средство анализа кода, которое проверяет сборки управляемого кода .NET на соответствие рекомендациям по проектированию microsoft платформа .NET Framework. Он использует отражение, синтаксический анализ MSIL и анализ графов вызовов для проверки сборок на наличие более 200 дефектов в следующих областях: соглашения об именовании, проектирование библиотек, локализация, безопасность и производительность. FxCop включает версии средства с графическим интерфейсом и командной строкой, а также пакет SDK для создания собственных правил. Дополнительные сведения см. на веб-сайте FxCop . Корпорация Майкрософт разрабатывает дополнительные правила FxCop, которые помогут вам выполнить миграцию.
Существуют также функции управляемой библиотеки, которые помогут вам во время выполнения определить, в какой среде вы работаете.
- System.IntPtr.Size — для определения того, работаете ли вы в 32-разрядном или 64-разрядном режиме.
- System.Reflection.Module.GetPEKind — для программного запроса .exe или .dll, чтобы узнать, предназначен ли он для выполнения только на определенной платформе или в WOW64.
Нет конкретного набора процедур для решения всех проблем, с которыми можно столкнуться. Этот технический документ предназначен для повышения осведомленности об этих проблемах и представления вам возможных альтернатив.