P/Invoke и 64-битная разработка

.NET
Недавно в моем WPF-приложении снова возникла потребность в ClearType постпроцессинге, т.е. добавлении ClearType-образных свойств графике которая изначально рендерится «обычным» способом. Снова был выбран unmanaged код (Intel C++ stack, OpenMP), но в этот раз взаимодействовать захотелось в 64-битной среде. Вот рассказ о том, насколько это было «весело».


Это какие-то неправильные пчёлы
Делаю я значит 64-битное С++ Win32 DLL приложение… и первое что я вижу после того как визард нагенерил файлы – это вот эту прелесть:




Если быть честным – опция x64 появляется в качестве конфигураций где-то в 50% случаев причем происходит это без особых на то причин. Тем не менее, пришлось покопаться в интернете для того чтобы понять, что нужно создавать конфигурацию в Configuration Manager. Сказано – сделано. Конфигурацию x64 можно в принципе выбрать из списка.




Решив сэкономить время на конфигурации, я решил скопировать настройки Win32. Все сработало замечательно за тем лишь исключением что все .Net-проекты в конфигурации стали таргетить Win64. А ведь проект бы скомпилился, а я бы ничего не узнал – если бы у меня не был подвязан PostSharp. Не знаю в чем косяк, но PostSharp «вынесло» в 64-битной конфе с такими error messages, что в экран не помещались.

Пришлось менять конфигурацию вручную.

Стоп, а где моя DLLка?
Вообще, чтобы построить DLLку, недостаточно просто при открытом файле из проекта нажать Build Project. В отличии от C# проектов (а также от Microsoft C++ проектов), опции Build Project на открытом файле попросту нет! Приходится правой кнопкой щелкать по решению и выбирать Build. Не знаю кто в Intel отвечает за юзабельность, но ИМХО это серьезный косяк.




Одна головоломка которая ожидает новичков в С++ разработке в VS это то, что DLL генерируется, но ее потом не найти. Попытка найти DLL в папочке Debug или Release соответствующего проекта даст вот это:




Проблема в том, что Visual C++ кладет сам DLL на уровне солюшна, а не конкретного проекта. Так что ищите несколькими папками выше. Возможно в этом и есть какая-то логика но я как дотнетчик ее в упор не вижу.

Платим импортные пошлины
На этом этапе отказался работать [DllImport]. Точнее он и не начинал работать. .Net просто не находил меток, несмотря на то что я правильно прописал Calling Convention. Потратив полчаса в гугле, я пришел к выводу что DllImport не умеет работать с 64-битными библиотеками. Точнее работать-то он будет, но только после того как вы сделаете DUMBIN /EXPORTS и начнете определять P/Invoke методы вот так:

[DllImport("Typografix.Bitmap.dll", <br/>
CallingConvention = CallingConvention.Cdecl, <br/>
ExactSpelling = true, <br/>
EntryPoint = "?Coalesce@@YAXPEAE0HHH@Z")] // <-- whoops!
public static extern void Coalesce(IntPtr src, IntPtr dst, <br/>
int width, int height, int stride);<br/>

Я не буду тут комментировать «изящность» такого решения потому что всем наверняка итак все понятно. К сожалению, на этом этапе все еще ничего не работает.

Есть желание «забить»
В супер-мега-идеальном мире, .Net проект в момент собирания мог бы просечь что если я прописал у него зависимость на другой, С++ проект, то надо бы в папку Debug/Release копировать результат труда зависимого проекта. А в мире реальном приходится на главный проект делать Post-Build Step который скопирует DLLку в нужную папку. Причем делать это желательно именно для С++ проекта, т.к. сборка и запуск .Net-ного потребителя не всегда запускает Post-Build Step. Причины этого, думаю, очевидны.

Тем не менее, после всех этих манипуляций, проект заработал. Набравшись наглости, я добавил таки поддержку OpenMP, после чего приложение благополучно упало с ошибкой «An attempt was made to load an image in incorrect format» что, в переводе на человеческий язык означает, что либа подвязана на еще какую-то либу, которую не найти. Согласитесь, информативность текста ошибки просто поражает – особенно после того как вы проводите час за ковырянием настроек проекта основываясь на предположении, что вы действительно начали генерировать DLLку в каком-то «некорректном» формате.

В этой ситуации меня снова спас DUMPBIN. Оказалось, что в отличии от использования OpenMP под компилятором Microsoft, Intel’евский подвязан на некую библиотечку libiomp5md.dll которая естественно в папку bin не попадает (да и в переменную PATH конечно тоже). Разруливаем. Вроде работает.

Последний гвоздь
Код для моей x64-библиотеки был взят прямо из 32-битного прокта, поэтому изначально он работать не захотел (из-за изменений в арифметике указателей). Поэтому я тупо поставил брейкпоинт в моей DLLке, включил Unmanaged Code Debugging и нажал F5. Результат?




Я не буду здесь описывать те ужимки и прыжки которые я делал чтобы отладка заработала. Если коротко, то суть была в том, чтобы прописать мою managed программу как стартовый процесс для DLLки. После многочисленных попыток, я решил эту затею бросить. Наверняка есть возможность подобной отладки но лично мне кажется что нужно сидеть и тупо ждать 2010й студии где возможно эта проблема разрулена.

Закрываем дело
Несмотря на все мое неудовлетворение происходящим, библиотечку я доделал. Конечно о качественной отладке говорить не приходится, но мне повезло – сами алгоритмы достаточно простые и к тому же, они уже были проверены в 32-битной среде, так что оставалось только подчистить в голове понимание того, что sizeof(void*) != sizeof(DWORD). Все заработало, и работает сейчас, в фоне, пока я пишу этот пост (алгоритмы использованы для заголовков). На вскидку, эти алгоритмы работают в 15-20 раз быстрее, чем их .Netные аналоги.

Я в принципе понимаю, что возможно не стоит лезть в 64-битную разработку сейчас, когда для большинства задач это абсолютно необязательно. Просто очень хотелось попробовать, понаступать на грабли. И несмотря на то, что часть проблем которые я описал наверняка являются последствием моей собственной тупости и неосведомленности, сам мезанизм разработки 64-битных приложений кажется мне черезчур сложным и запутанным. К тому же, на данный момент мне кажется более уместным для рисования текста использовать технологии Direct2D и DirectWrite, которые я активно изучаю и о которых я возможно напишу на Хабре.

Вот и все, спасибо за внимание! До новых встреч!
Tags:.netcsharpcpinvokeinterop64-bit
Hubs: .NET
+5
2.1k 8
Comments 36

Popular right now

.NET C# Software Engineer
from 3,500 to 4,000 $Hand2NoteRemote job
Senior .Net Engineer (C#)
to 230,000 ₽ItivitiСанкт-Петербург
.NET C# middle developer
from 140,000 to 160,000 ₽WB—TechМосква
.net developer
from 100,000 to 200,000 ₽БАРС ГрупКазаньRemote job
.NET developer
to 200,000 ₽SibedgeМоскваRemote job

Top of the last 24 hours