Не так много разработчиков осознают, что разрабатывая приложения для платформы Windows Mobile с использованием Compact Framework, у них существуют шансы собрать это же приложение под десктоп версию Windows! Я и сам об этом долгое время только задумывался, предполагая, что подобная возможность есть, но не рассматривал её как нечто, хоть сколько-нибудь реальное.
Существует несколько важных аспектов, которые нужно понимать, чтобы успешно организовать кросс-платформенную сборку. Я потратил прилично времени, собирая обломки знаний в разных местах сети, причём местами было настолько нетривиально, что решил поделиться с хабрасообществом тонкостями.
Во-первых, необходимо изначально создавать приложение для Windows Mobile (т.е. это основная платформа). Это действительно важно. Причин несколько, но основная заключается в том, что Compact Framework на то и компактный, что там существенно меньше классов и свойств у классов. Т.е. совместимость с десктопом есть, но односторонняя, т.е. только в сторону десктопа.
Во-вторых, нужно понимать, что отличия в приложении всё-таки будут и их придётся программировать отдельно. Например, стандартное меню, находящееся внизу на Windows Mobile автоматически перемещается наверх, и там Cancel и More выглядят не очень привлекательно. Далее, на десктопе в принципе нет InputPanel. Т.е. по сути нужно быть готовым к инструкциям компилятору #if #else #endif.
В-третьих, надо также готовиться к тому, что будут некоторые ограничения, касающиеся дизайна форм. А именно, нельзя открывать форму визуальным редактором при десктопе, выбранном в качестве текущего таргета — сразу же в *.Designer.cs налетит множество свойств, не поддерживаемых мобильным фреймворком — придётся откатывать.
В-четвёртых, придётся вручную править *.csproj файлы и видеть в Solution Explorer-е жёлтые треугольники — это нормально :)
В пятых, не все сборки и неймспесы на 100% работают на десктопе. Например, я совершенно не уверен в том, что SQL Server Compact собирается на десктопе. Не проверял, поэтому не обещаю. Точно знаю, что с SQLite всё хорошо (хотя и придётся попотеть немного).
В-шестых, нужно разобраться, как можно отлаживать приложение на десктопе, ведь все знают, что при сборке мобильного приложения необходимо выбрать некое устройство, физическое или эмулятор!
Попробуем разобраться с основными тонкостями.
Начнём с того, что у нас должен быть некоторый проект, созданный для Compact Framework. Создадим новый таргет через Build->Configuration Manager:

После этого добавим символ условной компиляции в свойствах проекта:

Начало положено. Посмотрим теперь, что делать, чтобы обеспечить подключение правильных сборок в нужном таргете. По умолчанию в нашем csproj файле нет никаких разделений по таргетам:
Чтобы иметь полный контроль, необходимо организовать примерно следующий финт:
Как видно, мы разделяем блоки ItemGroup по таргетам и указываем разные пути до сборок. Признаюсь, что именно здесь я возился дольше всего в моём проекте. Правильные версии выдирались прямо из сообщений об ошибках компиляции ;) Т.е. компилятор ругался, что не находятся такие-то нужные сборки и я подставлял правильные значения. И именно здесь я несколько раз порывался бросить всё, т.к. гуглятся подобные ошибки очень плохо.
В результате хитрых манипуляций с csproj файлом получается такая ерунда в Solution Explorer:

Это нормально, т.к. Visual Studio не совсем пригодна к таким извращениям (хотя и позволяет их в итоге).
Когда сборки подключаются правильные, мы имеем возможность для каждой платформы использовать тот максимум, который каждая из платформ поддерживает. Напомню, однако, что автоматическая кодогенерация дизайнера форм на�� тут всё портит. Поэтому необходимо, по возможности, сначала всё надизайнить, а потом уже править только руками — ведь при перегенерации *.Designer.cs, студия не сохраняет наши правки и добавления #if endif участков.
Немного хитрый момент. Мой проект поддерживает как QVGA, так и VGA разрешение, однако, в Designer.cs у форм свойство ClientSize всегда соответствует QVGA разрешению. На десктопе же иметь окно размером 240х268 как-то неправильно, особенно, когда есть VGA-скин. Поэтому в конструкторе после InitializeComponent() я помещаю участок условной компиляции:
Как видно, у меня объявлено две переменные типа Size. Зачем это нужно? Просто-напросто, у меня по F9 происходит переключение ClientSize, для эмуляции смены ориентации экрана. Полезно, когда необходимо протестировать OnResize. Да и в конце концов, есть же нетбуки с 800х480, для них ландшафтная ориентация — единственно возможная, чтобы всё поместилось на экране :)
Также видно, что MouseWheel тоже обрабатывается только на десктопе.
Есть удобный способ указывать, для какого таргета собирать некоторый метод:
В данном способе хорошо то, что вызовы данного метода могут существовать в коде, но при наличии данного атрибута, вызовы будут просто проигнорированы на других таргетах! Альтернатива с использованием #if #endif предполагает, что везде, где нужен вызов, необходимо проставить проверку, чтобы компилировать или не компилировать вызов на нужной платформе.
Существует два способа отладки. Первый (неудобный) заключается в том, мы идём в bin\Desktop\, запускаем exe-файл и потом в студии говорим Debug — Attatch to process. Этот способ сначала кажется единственно возможным. Но! Есть мега-хак, неофициальный и неподдерживаемый способ (который, тем не менее, работает как в VS2005, так и в VS2008). Способ следующий:
Ну вот, теперь нам доступен отладчик и все вкусности десктопной отладки.
Чтож, в статье перечислены основные подводные камни, с которыми я столкнулся в процессе сборки своего проекта под Windows. Далее всё было делом техники — ловил исключения и разбирался в их причине. Среди них были не найденные пути, которые, очевидно, на десктопе отличаются и т.д., но это всё уже было ничто, по сравнению с начальными проблемами.
PS. Почти всё, что описано выше, я выстрадал в результате долгих поисков, и вот, в самом конце, когда я искал способ отладки на десктопе, я натолкнулся на шикарную статью Дениела Моса о кросс-платформенной компиляции для Compact Framework :) Моя статья ни в коем случае не является переводом, однако, не могу не дать на неё ссылку.
Существует несколько важных аспектов, которые нужно понимать, чтобы успешно организовать кросс-платформенную сборку. Я потратил прилично времени, собирая обломки знаний в разных местах сети, причём местами было настолько нетривиально, что решил поделиться с хабрасообществом тонкостями.
Во-первых, во-вторых, в-третьих...
Во-первых, необходимо изначально создавать приложение для Windows Mobile (т.е. это основная платформа). Это действительно важно. Причин несколько, но основная заключается в том, что Compact Framework на то и компактный, что там существенно меньше классов и свойств у классов. Т.е. совместимость с десктопом есть, но односторонняя, т.е. только в сторону десктопа.
Во-вторых, нужно понимать, что отличия в приложении всё-таки будут и их придётся программировать отдельно. Например, стандартное меню, находящееся внизу на Windows Mobile автоматически перемещается наверх, и там Cancel и More выглядят не очень привлекательно. Далее, на десктопе в принципе нет InputPanel. Т.е. по сути нужно быть готовым к инструкциям компилятору #if #else #endif.
В-третьих, надо также готовиться к тому, что будут некоторые ограничения, касающиеся дизайна форм. А именно, нельзя открывать форму визуальным редактором при десктопе, выбранном в качестве текущего таргета — сразу же в *.Designer.cs налетит множество свойств, не поддерживаемых мобильным фреймворком — придётся откатывать.
В-четвёртых, придётся вручную править *.csproj файлы и видеть в Solution Explorer-е жёлтые треугольники — это нормально :)
В пятых, не все сборки и неймспесы на 100% работают на десктопе. Например, я совершенно не уверен в том, что SQL Server Compact собирается на десктопе. Не проверял, поэтому не обещаю. Точно знаю, что с SQLite всё хорошо (хотя и придётся попотеть немного).
В-шестых, нужно разобраться, как можно отлаживать приложение на десктопе, ведь все знают, что при сборке мобильного приложения необходимо выбрать некое устройство, физическое или эмулятор!
Попробуем разобраться с основными тонкостями.
Desktop Target
Начнём с того, что у нас должен быть некоторый проект, созданный для Compact Framework. Создадим новый таргет через Build->Configuration Manager:

После этого добавим символ условной компиляции в свойствах проекта:

Начало положено. Посмотрим теперь, что делать, чтобы обеспечить подключение правильных сборок в нужном таргете. По умолчанию в нашем csproj файле нет никаких разделений по таргетам:
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
* This source code was highlighted with Source Code Highlighter.
Чтобы иметь полный контроль, необходимо организовать примерно следующий финт:
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<Reference Include="mscorlib">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.WindowsMobile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\Program Files\Windows Mobile 6 SDK\Managed Libraries\Microsoft.WindowsMobile.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsMobile.Status, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\Program Files\Windows Mobile 6 SDK\Managed Libraries\Microsoft.WindowsMobile.Status.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsCE.Forms">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data">
<Private>False</Private>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Drawing" />
<Reference Include="System.Data.SQLite, Version=1.0.60.0, Culture=neutral, PublicKeyToken=1fdb50b1b62b4c84, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\..\..\..\Program Files\SQLite.NET\bin\CompactFramework\SQLite.Interop.060.DLL</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Desktop|AnyCPU' ">
<Reference Include="mscorlib">
<Private>False</Private>
<HintPath>C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll</HintPath>
</Reference>
<Reference Include="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86">
<HintPath>C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms">
<HintPath>C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll</HintPath>
</Reference>
<Reference Include="System.Drawing">
<HintPath>C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll</HintPath>
</Reference>
<Reference Include="System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86" />
<Reference Include="System.Data.SQLite, Version=1.0.60.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
<Private>True</Private>
<HintPath>..\..\..\..\..\Program Files\SQLite.NET\bin\System.Data.SQLite.DLL</HintPath>
</Reference>
</ItemGroup>
* This source code was highlighted with Source Code Highlighter.
Как видно, мы разделяем блоки ItemGroup по таргетам и указываем разные пути до сборок. Признаюсь, что именно здесь я возился дольше всего в моём проекте. Правильные версии выдирались прямо из сообщений об ошибках компиляции ;) Т.е. компилятор ругался, что не находятся такие-то нужные сборки и я подставлял правильные значения. И именно здесь я несколько раз порывался бросить всё, т.к. гуглятся подобные ошибки очень плохо.
В результате хитрых манипуляций с csproj файлом получается такая ерунда в Solution Explorer:

Это нормально, т.к. Visual Studio не совсем пригодна к таким извращениям (хотя и позволяет их в итоге).
#if #endif
Когда сборки подключаются правильные, мы имеем возможность для каждой платформы использовать тот максимум, который каждая из платформ поддерживает. Напомню, однако, что автоматическая кодогенерация дизайнера форм на�� тут всё портит. Поэтому необходимо, по возможности, сначала всё надизайнить, а потом уже править только руками — ведь при перегенерации *.Designer.cs, студия не сохраняет наши правки и добавления #if endif участков.
Немного хитрый момент. Мой проект поддерживает как QVGA, так и VGA разрешение, однако, в Designer.cs у форм свойство ClientSize всегда соответствует QVGA разрешению. На десктопе же иметь окно размером 240х268 как-то неправильно, особенно, когда есть VGA-скин. Поэтому в конструкторе после InitializeComponent() я помещаю участок условной компиляции:
Size vertSize = new Size(480, 560);
Size horisSize = new Size(640, 480);
[...]
InitializeComponent();
#if Desktop
this.ClientSize = vertSize;
this.FormBorderStyle = FormBorderStyle.Fixed3D;
this.MaximizeBox = false;
this.MouseWheel += new MouseEventHandler(MainController_MouseWheel);
#endif
* This source code was highlighted with Source Code Highlighter.
Как видно, у меня объявлено две переменные типа Size. Зачем это нужно? Просто-напросто, у меня по F9 происходит переключение ClientSize, для эмуляции смены ориентации экрана. Полезно, когда необходимо протестировать OnResize. Да и в конце концов, есть же нетбуки с 800х480, для них ландшафтная ориентация — единственно возможная, чтобы всё поместилось на экране :)
Также видно, что MouseWheel тоже обрабатывается только на десктопе.
System.Diagnostics.Conditional
Есть удобный способ указывать, для какого таргета собирать некоторый метод:
[Conditional(“Desktop”)]
public void SomeDesktopMethod()
{
}
* This source code was highlighted with Source Code Highlighter.
В данном способе хорошо то, что вызовы данного метода могут существовать в коде, но при наличии данного атрибута, вызовы будут просто проигнорированы на других таргетах! Альтернатива с использованием #if #endif предполагает, что везде, где нужен вызов, необходимо проставить проверку, чтобы компилировать или не компилировать вызов на нужной платформе.
Отладка на десктопе
Существует два способа отладки. Первый (неудобный) заключается в том, мы идём в bin\Desktop\, запускаем exe-файл и потом в студии говорим Debug — Attatch to process. Этот способ сначала кажется единственно возможным. Но! Есть мега-хак, неофициальный и неподдерживаемый способ (который, тем не менее, работает как в VS2005, так и в VS2008). Способ следующий:
- В студии открыть Tools/Options, далее в дереве выбрать Device Tools/Devices.
- В выпадающем меню выбрать платформу, для которой необходимо организовать отладку на десктопе. Это будет необходимо повторить для всех платформ, где необходимо.
- Далее нужно выбрать любой из эмуляторов и нажать [Save As…]. Удобно назвать копию “My Computer”.
- Теперь надо закрыть студию и открыть %USERPROFILE%\Local Settings\Application Data\Microsoft\CoreCon\1.0\conman_ds_platform.xsl файл в текстовом редакторе.
- В файле необходимо найти <DEVICE …> элемент, соответствующий свежесозданному «дивайсу»
- Далее добавляем следующую строку — <PROPERTY ID=«IsDesktopDevice» Name=«IsDesktopDevice»>true</PROPERTY>
сразу после тега <PROPERTYCONTAINER>. - Сохраняем conman_ds_platform.xsl и перезапускаем студию
Ну вот, теперь нам доступен отладчик и все вкусности десктопной отладки.
Заключение
Чтож, в статье перечислены основные подводные камни, с которыми я столкнулся в процессе сборки своего проекта под Windows. Далее всё было делом техники — ловил исключения и разбирался в их причине. Среди них были не найденные пути, которые, очевидно, на десктопе отличаются и т.д., но это всё уже было ничто, по сравнению с начальными проблемами.
PS. Почти всё, что описано выше, я выстрадал в результате долгих поисков, и вот, в самом конце, когда я искал способ отладки на десктопе, я натолкнулся на шикарную статью Дениела Моса о кросс-платформенной компиляции для Compact Framework :) Моя статья ни в коем случае не является переводом, однако, не могу не дать на неё ссылку.