C ++ API дизайн и обработка ошибок - PullRequest
4 голосов
/ 29 января 2012

Мне нужно написать C ++ API, который состоит из нескольких экспортированных классов C ++, предоставляемых из Dll, с использованием файлов .lib (MSVC). Из ответа на другой мой вопрос я понимаю, что экспортированные методы класса не могут использовать исключения, в случае, если C ++ API построен в одной версии VC ++ (скажем, 2010), а клиентский код написан в другой версии VC ++. Поскольку исключения не могут быть частью общедоступного интерфейса API, я ищу другую стратегию обработки ошибок. Мои ограничения: я не хочу использовать COM, и мне не хватает богатой системы кодов ошибок (например, HRESULT). Я хочу иметь класс, похожий на исключение, который содержит код ошибки, сообщение об ошибке и любую другую информацию, которая мне нужна. Кроме того, я не хочу делать отдельную сборку для каждой версии VC ++.

Мой текущий подход заключается в следующем. Каждый открытый метод класса возвращает перечисляемое значение (например, ErrorCode). В случае сбоя метода статическая функция, такая как GetLastErrorInfo, возвращает указатель на класс C ++ (скажем, ErrorInfo), который содержит информацию об ошибках досягаемости. ErrorInfo хранится как специфичные для потока данные и содержит информацию об ошибке последнего вызова в текущем потоке. Если последний вызов API завершился успешно, GetErrorInfo возвращает NULL.

Рассмотрим этот код с исключениями:

try
{
    classPtr->DoSomething();
    cout << classPtr->GetData() << endl;
}
catch(const MyException& ex)
{
    cout << ex.GetErrorMessage() << endl;
    return;
}

Без исключения это выглядит так:

ErrorCode result;
int data;
result = classPtr->DoSomething();
if ( result != Success )
{
    cout &lt&lt MyClass::GetLastErrorInfo()->GetErrorMessage() &lt&lt endl;
    return;
}
result = classPtr->GetData(data);
if ( result != Success )
{
    cout &lt&lt MyClass::GetLastErrorInfo()->GetErrorMessage() &lt&lt endl;
    return;
}
cout << data &lt&lt endl;

Это не выглядит хорошо. Интерфейс класса запутан: у каждой функции теперь есть тип возврата ErrorCode. Возвращаемые значения становятся выходными параметрами. Есть ли лучший подход, позволяющий получать информацию об ошибках и поддерживать чистый интерфейс API?

Ответы [ 3 ]

6 голосов
/ 29 января 2012

Возможно, вы пропускаете простое решение. Единственное ограничение - исключение не может пересекать границу модуля. Нет проблем с клиентским кодом , который сам вызывает исключение Поэтому предоставьте встроенную функцию в заголовке, скажем, CheckReturn (), которая генерирует исключение rich.

Для вдохновения посмотрите на интерфейс COM IErrorInfo и связанный с ним _com_error class . Они решают точно такую ​​же проблему. Также обратите внимание на директиву #import, доступную в MSVC, она автоматически генерирует небольшие функции-обертки, которые выполняют вызов и выдают исключение для возвращаемого значения ошибки. Но вы не хотите использовать COM, поэтому его нельзя использовать напрямую.

2 голосов
/ 29 января 2012

Вы должны быть чрезвычайно осторожны, если возвращаете объекты C ++ из .dll, потому что вызывающая сторона может попытаться использовать эти объекты способами, которые включают в себя их копирование или удаление. Если вызывающая сторона не использует одну и ту же кучу (или одну и ту же стандартную библиотеку), вы будете иметь все виды сбоев и утечек памяти повсюду. По моему опыту, передача объектов C ++ через границы DLL - плохая идея.

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

Кроме того, при экспорте классов C ++ из .dll маловероятно, что что-либо, кроме C ++, может использовать этот .dll, поскольку другие языки имеют различные схемы памяти для объектов. Даже разные компиляторы C ++ могут иметь проблемы с использованием этих классов.

Перечитывая ваши ограничения, я бы сказал, что создание COM-объектов - один из ваших лучших вариантов, поскольку он дает вам гибкость и возможность возвращать объекты со сложными данными (хотя и не объекты C ++).

1 голос
/ 29 января 2012

Как насчет этого:

Основные данные об ошибках - вы можете расширить для "других вещей" вашей библиотеки:

namespace MON {
  class t_error_description {
  public:
    t_error_description(const int& code, const std::string& message);
    virtual ~t_error_description(); /* << allow any other info via subclass */
  public:
    virtual void description(std::ostream& stream) const;
    /* … */
  private:
    const int d_code;
    const std::string d_message;
  };
}

Основная ошибка контейнера. Оборачивает все относительно описания, и это все, с чем клиент имеет дело напрямую:

namespace MON {
  class t_error {
  public:
    t_error();
    ~t_error();
  public:
    /* or perhaps you'd favor a stream op? */
    void description(std::ostream&) const;
    /* sets the error - this is a take operation */
    void set(const t_error_description* const desc);

    void clear();
    /* … */
  private:
    /* trivial construction */
    t_auto_pointer<const t_error_description> d_errorDescription;
  private:
    /* verboten */
    t_error(const t_error&);
    t_error& operator=(const t_error&);
  };
}

Базовый вызов lib:

namespace MON {
  /* return false on error */
  bool DoSomething(t_error& outError) {
    if (Foo()) {
      outError.set(new t_error_description(ErrorCodeThingy, "blah blah"));
      return false;
    }
    return true;
  }
}

Клиентский звонок:

MON::t_error err;
if (!MON::DoSomething(err)) {
  log << "cannot do anything!\nError: ";
  err.description(log);
  return;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...