Не так много разработчиков осознают, что разрабатывая приложения для платформы 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 всё хорошо (хотя и придётся попотеть немного).

В-шестых, нужно разобраться, как можно отлаживать приложение на десктопе, ведь все знают, что при сборке мобильного приложения необходимо выбрать некое устройство, физическое или эмулятор!

Попробуем разобраться с основными тонкостями.

Desktop Target


Начнём с того, что у нас должен быть некоторый проект, созданный для Compact Framework. Создадим новый таргет через Build->Configuration Manager:
Configuration Manager

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

Начало положено. Посмотрим теперь, что делать, чтобы обеспечить подключение правильных сборок в нужном таргете. По умолчанию в нашем 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:
Solution Explorer Warnings

Это нормально, т.к. 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). Способ следующий:
  1. В студии открыть Tools/Options, далее в дереве выбрать Device Tools/Devices.
  2. В выпадающем меню выбрать платформу, для которой необходимо организовать отладку на десктопе. Это будет необходимо повторить для всех платформ, где необходимо.
  3. Далее нужно выбрать любой из эмуляторов и нажать [Save As…]. Удобно назвать копию “My Computer”.
  4. Теперь надо закрыть студию и открыть %USERPROFILE%\Local Settings\Application Data\Microsoft\CoreCon\1.0\conman_ds_platform.xsl файл в текстовом редакторе.
  5. В файле необходимо найти <DEVICE …> элемент, соответствующий свежесозданному «дивайсу»
  6. Далее добавляем следующую строку — <PROPERTY ID=«IsDesktopDevice» Name=«IsDesktopDevice»>true</PROPERTY>
    сразу после тега <PROPERTYCONTAINER>.
  7. Сохраняем conman_ds_platform.xsl и перезапускаем студию


Ну вот, теперь нам доступен отладчик и все вкусности десктопной отладки.

Заключение


Чтож, в статье перечислены основные подводные камни, с которыми я столкнулся в процессе сборки своего проекта под Windows. Далее всё было делом техники — ловил исключения и разбирался в их причине. Среди них были не найденные пути, которые, очевидно, на десктопе отличаются и т.д., но это всё уже было ничто, по сравнению с начальными проблемами.

PS. Почти всё, что описано выше, я выстрадал в результате долгих поисков, и вот, в самом конце, когда я искал способ отладки на десктопе, я натолкнулся на шикарную статью Дениела Моса о кросс-платформенной компиляции для Compact Framework :) Моя статья ни в коем случае не является переводом, однако, не могу не дать на неё ссылку.