从C共享库传回错误字符串的好方法是线程安全的

时间:2010-12-22 23:41:31

标签: c++ c shared-libraries

我正在编写一个供内部使用的C共享库(如果这很重要,我将dlopen()用于c ++应用程序)。共享库通过JNI模块加载(除此之外)一些Java代码,这意味着所有恶梦错误模式的方式都可以来自我需要在应用程序中智能处理的JVM。此外,该库需要重入。在这种情况下是否存在用于传递错误字符串的习惯用法,或者我是否将映射错误粘贴到整数并使用printfs进行调试?

谢谢!

7 个答案:

答案 0 :(得分:3)

我对问题的解决方法与其他人的方法略有不同。他们没有错,只是因为我不得不在这个问题的另一个方面进行斗争。

  1. C API需要提供数字错误代码,以便使用API​​的代码可以采取合理的措施在适当的时候从错误中恢复,并在没有时传递它们。 errno.h代码表明错误的良好分类;实际上,如果您可以重用这些代码(或者只是传递它们,例如,如果您的所有错误最终来自系统调用),请执行此操作。
    • 复制errno本身。如果可能,直接从可能失败的函数返回错误代码。如果无法做到这一点,请在状态对象上使用GetLastError()方法。你有一个状态对象,是吗?
  2. 如果您必须创建自己的代码(errno.h代码不会删除它),请提供类似于strerror的功能,将这些代码转换为人类可读的字符串。
    • 翻译这些字符串可能适合也可能不适合。如果它们只是由开发人员阅读,请不要打扰。但是如果你需要向最终用户展示它们,那么是的,你需要翻译它们。
    • 这些字符串的未翻译版本应该只是字符串常量,因此您没有分配问题。但是,不要浪费时间和精力来编写自己的翻译基础架构。使用GNU gettext
  3. 如果您的代码在另一段代码之上分层,那么至关重要可以直接访问该代码生成的所有错误信息和相关上下文信息,并且您可以轻松实现针对您的代码的开发人员将最终用户的所有信息包含在错误消息中。
    • 例如,如果您的库产生自己设计的错误代码是系统调用失败的直接后果,那么您的状态对象需要返回在系统调用失败后立即观察到的errno值的方法,名称涉及的文件(如果有的话),理想情况下也是系统调用本身的名称。人们经常会错误地看待这种错误 - 例如,SQLite,否则是一个设计良好的API,不会暴露errno值或文件的名称,这使得难以区分“文件权限”数据库错误“来自”你的代码中有一个错误“。
  4. 编辑:附录:该领域的常见错误包括:

    • 保存您的API(例如使用out参数),以便自然返回其他值的函数可以返回错误代码。
    • 没有为调用者公开足够的细节,以便能够生成允许知识渊博的人解决问题的错误消息。 (这位知识渊博的人可能不是最终用户。可能是您的错误消息只会出现在服务器日志文件或崩溃报告中,仅供开发人员使用。)
    • 在错误中暴露出太多不同的细微区别。如果您的呼叫者永远不会在两个不同的错误代码中做出不同的事情,那么它们应该是相同的代码。
    • 提供多个成功代码。这要求微妙的错误。

    另外,请仔细考虑应该允许哪些API失败。以下是一些永远不会失败的事情:

    • 只读数据访问器,尤其是那些返回标量的数据访问器,最特别那些返回布尔值的数据访问器。
    • 最一般意义上的析构函数。 (这是UNIX内核API中的一个经典错误:closemunmap不应该失败。值得庆幸的是,至少_exit不能。)
    • 如果abort失败,您应立即致电malloc,而不是尝试将其传播给来电者。 (由于异常和RAII,在C ++中不是这样 - 如果你很幸运能够开发一个正确使用这两者的C ++项目。)

    结束时:有关如何处理所有错误的示例,请查看XPCOM

答案 1 :(得分:2)

您返回指向static const char []个对象的指针。这始终是处理错误字符串的正确方法。如果需要将它们本地化,则返回指向只读内存映射本地化字符串的指针。

答案 2 :(得分:1)

在C中,如果您没有国际化(I18N)或本地化(L10N)需要担心,那么指向常量数据的指针是提供错误消息字符串的好方法。但是,您经常发现错误消息需要一些支持信息(例如无法打开的文件的名称),这些信息实际上不能由常量数据处理。

考虑到I18N / L10N,我建议将每种语言的固定消息字符串存储在格式正确的文件中,然后在分叉任何线程之前使用mmap()将文件“读取”到内存中。这样映射的区域应该被视为只读(在PROT_READ的调用中使用mmap()。)

这可以避免内存管理的复杂问题,并避免内存泄漏。

考虑是否提供可以调用以获取最新错误的函数。它可以有一个原型,如:

int get_error(int errnum, char *buffer, size_t buflen);

我假设其他函数调用返回错误号;库函数然后查询它有关当前线程的任何线程安全内存以及返回给该线程的最后一个错误条件,并将适当的错误消息(可能被截断)格式化为给定的缓冲区。

使用C ++,您可以从错误报告机制返回(引用)标准字符串;这意味着您可以格式化字符串以包含文件名或其他动态属性。收集信息的代码将负责释放字符串,由于C ++具有析构函数,因此不会(不应该)成为问题。您可能仍希望使用mmap()来加载消息标记的格式字符串。

您需要注意加载的文件,特别是用作格式字符串的任何字符串。 (另外,如果您正在处理I18N / L10N,您需要担心是否使用'n$表示法来允许参数重新排序;并且您必须担心不同文化/语言关于订单的不同规则其中出现一个句子的单词。)

答案 3 :(得分:0)

我想你可以像Windows一样使用PWideChars。它的线程安全。你需要的是调用应用程序创建一个PllChar,Dll将用它来设置错误。然后,callling app需要读取该PWideChar并释放其内存。

答案 4 :(得分:0)

R上。有一个很好的答案(使用静态const char []),但如果您将有各种口语,我喜欢使用Enum来定义错误代码。这比一些名称的#define更好于int值。

答案 5 :(得分:0)

  • 返回整数,不设置一些全局变量(如errno - 即使它可能被实现TLS);感谢Linux内核的return -ENOENT;
  • 风格
  • 具有类似于strerror的函数,它接受这样的整数并返回指向const字符串的指针。如果需要,此函数也可以透明地执行I18N,因为gettext-returnable字符串在翻译数据库的生命周期内也保持不变。

答案 6 :(得分:0)

如果您需要提供非静态错误消息,那么我建议返回这样的字符串:error_code_t function(,char ** err_msg)。然后提供一个释放错误消息的函数:void free_error_message(char * err_msg)。这样,您可以隐藏错误字符串的分配和释放方式。这当然只值得实现您的错误字符串本质上是动态的,这意味着它们不仅仅传达错误代码的翻译。

请用mu格式来监督疏忽。我是用手机写的......