Обертывание неуправляемой библиотеки классов C ++ с C ++ / CLI - Вопрос 1 - Организация проекта / кода - PullRequest
9 голосов
/ 27 января 2011

Примечание: Эта публикация представляет Вопрос № 1 моего запроса.Вводный блок (весь текст до тех пор, пока не набраны цифры) повторяется в обоих вопросах, поскольку для ответа на вопрос может понадобиться справочная информация.


Введение в вопрос

Iиметь неуправляемую библиотеку C ++, которая содержит классы и функции, которые являются общими и используются несколькими библиотеками «более высокого уровня».Теперь мне нужно предоставить доступ к общей библиотеке для приложений C # / .Net.Для этого мне придется обернуть общую библиотеку классами-обертками C ++ / CLI.

Классы, содержащиеся в общей библиотеке, могут быть сложными классами, содержащими вложенные определения классов и переменные-члены, которые являются коллекциями других объектов классов.Переменные коллекции - это экземпляры typedef пользовательского класса списка для управления коллекцией.Общая библиотека также включает в себя классы, которые представляют синтаксическую структуру пользовательского синтаксиса файла сценария, созданного с использованием FLEX / BISON.Общая библиотека и библиотеки «более высокого уровня» написаны таким образом, который допускает кросс-платформенную (Linux и GCC) компиляцию и использование.Любые изменения, которые я делаю, должны допускать это.

Классы-оболочки C ++ / CLI сначала должны иметь только возможность чтения.Но по мере продвижения проекта мне в конечном итоге потребуется возможность создавать и изменять объекты.

Я знаю C ++ / CLI и создал несколько оболочек для других неуправляемых проектов C / C ++, а также предоставляюабстрактные функциональные возможности для этой же общей библиотеки.Итак, у меня уже есть основы (и некоторые дополнительные знания).

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


Актуальные вопросы

  1. Как мне структурировать файлы в проекте?

    • Пространство имен и имена классов между неуправляемым и C ++ / CLI проектами не будут конфликтовать.Так как неуправляемый проект использует префикс «C» для имен классов, а C ++ / CLI - нет.Так что неуправляемый класс CWidget станет просто Widget.И они используют разные имена корневых пространств имен.

    • Проблема возникает, когда речь идет об именах файлов.В качестве шаблона именования по умолчанию я буду использовать Widget.h и Widget.cpp как для неуправляемого, так и для C ++ / CLI.

    • В настоящее время проекты настроены так, что все файлы проекта находятся вкорень папки проекта.Включения для заголовочных файлов выполняются как просто имя заголовка (например, #include "Widget.h").И для правильного разрешения включений для файлов другого проекта путь к другому проекту добавляется в свойство Additional Include Directories используемого проекта.

    • Если я изменяю свой Additional Include Directoriesсвойство, являющееся корнем решения (..\) и включенное для неуправляемого заголовка как #include "Unmanaged\Widget.h, у меня есть новая проблема разрешения заголовков, включенная в неуправляемый заголовок.Таким образом, использование этой опции потребует, чтобы я изменил все операторы include для добавления префикса к каталогу проекта.Я знаю другие проекты

    • Наиболее очевидное / самое быстрое решение проблемы дублирующихся имен файлов - это изменение шаблона именования для одной из библиотек.Таким образом, для проекта C ++ / CLI вместо использования Widget.h и Widget.cpp в качестве префикса или суффикса m (управляемый) или w (оболочка).Таким образом, файлы C ++ / CLI будут mWidget.h, wWidget.h, WidgetM.h или WidgetW.h.Тогда я мог бы просто повторить свой существующий шаблон повсюду и быть хорошим.

    • Но есть ли лучший способ упорядочить файлы, чтобы я мог сохранить свои имена без префикса / суффикса сминимальные изменения в существующем коде?

  2. Упаковка неуправляемой библиотеки классов C ++ с C ++ / CLI - Вопрос 2 - Коллекции

1 Ответ

3 голосов
/ 28 января 2011

Я сделал нечто очень похожее, когда оборачивал неуправляемый API C ++.В моем случае даже имена классов были идентичны.Вот что я сделал с моим проектом:

  • Мое решение C ++ / CLI было на C:\Foo
  • Неуправляемый API C ++ был на C:\Foo\api
  • Каждый файл .h (bar.h) начинался с #include "api/bar.h"
  • Все использование неуправляемых классов было через Api::Bar

В моем случае большая часть времени, потраченного на проект, была связана с автоматизацией создания управляемых файлов C ++.Я сделал пару вручную, понял, сколько времени потребуется, чтобы все сделать таким образом, и начал автоматизировать процесс.

Мои библиотеки все еще были отдельными.У меня был один неуправляемый dll и один управляемый dll.Я просто сохранил неуправляемый проект в управляемом.

Что касается автоматизации, он будет читать каждый неуправляемый файл .h и создавать мой управляемый файл .h следующим образом:

  • Добавить мою #include "api/bar.h" строку.
  • Скопировать все #include строки.
  • Добавить мое управляемое пространство имен.
  • Дублировать класс какуправляемый класс, включая базовый класс.
  • Добавить защищенный указатель на неуправляемый класс.
  • Добавить функцию GetInner(), чтобы получить указатель неуправляемого класса.
  • Добавитьфинализатор для удаления неуправляемого класса.
  • Добавление нескольких функций, когда функция имеет параметры по умолчанию.

Затем создайте управляемые файлы .cpp, например:

  • Заполнить большинство функций вызовами неуправляемых функций
  • Использовать GetInner() каждый раз, когда класс включался в качестве параметров функции
  • Делать marshal_as для преобразования между строками и std :: wstrings

Это работало для большинства классов с небольшой ручной настройкой.Единственная область, где это не работало, была с коллекционными классами.Теперь я думаю, что мне повезло, так как каждый класс коллекции был просто оберткой вокруг std::vector.Я основал все свои управляемые версии на CollectionBase, с конструктором, принимающим класс неуправляемой коллекции, и методом со следующей сигнатурой:

void ToFoo(Api::Foo& v);

Таким образом, перейти из неуправляемой коллекции в управляемую коллекцию было просто gcnew, а пойти другим путем было:

Foo* foo; //most likely a function parameter, already assigned.
Api::Foo apifoo; //name just "api" + the name of the managed Foo.
foo->ToFoo(apifoo);

Это тоже быловстроенный в генерацию классов .cpp для классов, не являющихся коллекциями.

Код для ToFoo просто очищает неуправляемую коллекцию, выполняет итерацию по управляемой коллекции и добавляет каждый неуправляемый элемент с помощью GetInner().Мои коллекции были не такими большими, так что это работало на отлично.

Если бы мне пришлось снова делать классы коллекций, я бы, скорее всего, не стал бы основывать их на CollectionBase сейчас.Я бы с большей вероятностью основал их на List<T> или сделал бы нечто подобное тому, что обсуждалось в текущих ответах, опубликованных на ваш вопрос №2.Мой код был запущен с .Net 1.1 (без обобщений), поэтому в то время CollectionBase имел для меня наибольшее значение.

Он был обработан вручную с помощью Regex.Поскольку библиотека была написана очень последовательно, я смог прочитать весь файл, использовать Regex для удаления комментариев и других вещей, которые могут вызвать проблемы (встроенные функции), а затем просто прочитать каждую строку и Regex, чтобы выяснить, что в ней было.У меня были такие вещи, как список всех коллекций (чтобы знать, когда делать вызовы коллекций выше), ненормальные классы (string / std :: wstring) и другие вещи, которые я не помню.Я собираюсь посмотреть, войдет ли он в наш новый источник контроля, так как проект был заброшен пару лет назад.

...