从 C 共享库传回错误字符串的好方法是线程安全的
我正在编写一个供内部使用的 C 共享库(如果重要的话,我会将其 dlopen() 到 C++ 应用程序中)。共享库通过 JNI 模块加载(除其他外)一些 java 代码,这意味着我需要在应用程序中智能处理的 JVM 中可能会出现各种噩梦错误模式。此外,该库需要可重入。在这种情况下,是否有用于传递错误字符串的习惯用法,或者我是否坚持将错误映射到整数并使用 printfs 来调试?
谢谢!
I'm writing a C shared library for internal use (I'll be dlopen()'ing it to a c++ application, if that matters). The shared library loads (amongst other things) some java code through a JNI module, which means all manners of nightmare error modes can come out of the JVM that I need to handle intelligently in the application. Additionally, this library needs to be re-entrant. Is there in idiom for passing error strings back in this case, or am I stuck mapping errors to integers and using printfs to debug things?
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我解决问题的方法与其他人的方法略有不同。他们没有错,只是我不得不努力解决这个问题的另一个方面。
errno.h
代码展示了良好的错误分类;事实上,如果您可以重用这些代码(或者只是传递它们,例如,如果所有错误最终都来自系统调用),请这样做。errno
本身。如果可能,请直接从可能失败的函数返回错误代码。如果不可能,请在状态对象上使用GetLastError()
方法。你有一个状态对象,是吗?errno.h
代码不能解决问题),请提供一个类似于strerror
的函数,将这些代码转换为人类可读的代码字符串。errno
值,涉及的文件的名称(如果有),最好也是系统调用本身的名称。人们经常犯这个错误——例如,SQLite,否则是一个设计良好的 API,不会公开 errno 值或文件名,这使得很难区分“文件”数据库的权限错误”来自“您的代码中有错误”。编辑: 附录:该领域的常见错误包括:
另外,请仔细考虑哪些 API 应该允许失败。以下是一些永远不应该失败的事情:
close
和munmap
不应该失败。值得庆幸的是,至少_exit
可以” t.)malloc
失败,您应该立即调用abort
,而不是尝试将其传播给调用者。 能够从事正确使用这两种方法的 C++ 项目。)(由于异常和 RAII,这在 C++ 中并非如此——如果您很幸运 >错误,只需看看XPCOM。
My approach to the problem would be a little different from everyone else's. They're not wrong, it's just that I've had to wrestle with a different aspect of this problem.
errno.h
codes demonstrate a good categorization of errors; in fact, if you can reuse those codes (or just pass them along, e.g. if all your errors come ultimately from system calls), do so.errno
itself. If possible, return error codes directly from functions that can fail. If that is not possible, have aGetLastError()
method on your state object. You have a state object, yes?errno.h
codes don't cut it), provide a function analogous tostrerror
, that converts these codes to human-readable strings.errno
value observed immediately after the system call that failed, the name of the file involved (if any), and ideally also the name of the system call itself. People get this wrong waaay too often -- for instance, SQLite, otherwise a well designed API, does not expose theerrno
value or the name of the file, which makes it infuriatingly hard to distinguish "the file permissions on the database are wrong" from "you have a bug in your code".EDIT: Addendum: common mistakes in this area include:
Also, think very carefully about which APIs ought to be allowed to fail. Here are some things that should never fail:
close
andmunmap
should not be able to fail. Thankfully, at least_exit
can't.)abort
ifmalloc
fails rather than trying to propagate it to your caller. (This is not true in C++ thanks to exceptions and RAII -- if you are so lucky as to be working on a C++ project that uses both of those properly.)In closing: for an example of how to do just about everything wrong, look no further than XPCOM.
您返回指向
static const char []
对象的指针。这始终是处理错误字符串的正确方法。如果需要对它们进行本地化,则返回指向只读内存映射本地化字符串的指针。You return pointers to
static const char []
objects. This is always the correct way to handle error strings. If you need them localized, you return pointers to read-only memory-mapped localization strings.在 C 中,如果您无需担心国际化 (I18N) 或本地化 (L10N),那么指向常量数据的指针是提供错误消息字符串的好方法。然而,您经常发现错误消息需要一些支持信息(例如无法打开的文件的名称),而这些信息实际上无法通过常量数据来处理。
由于需要担心 I18N/L10N,我建议将每种语言的固定消息字符串存储在适当格式的文件中,然后使用
mmap()
在分叉任何线程之前将文件“读”到内存中。如此映射的区域应被视为只读(在调用mmap()
中使用PROT_READ
)。这避免了复杂的内存管理问题并避免内存泄漏。
考虑是否提供一个可以调用来获取最新错误的函数。它可以有一个原型,例如:
我假设错误号是由其他函数调用返回的;然后,库函数查询它拥有的有关当前线程和返回到该线程的最后一个错误条件的任何线程安全内存,并将适当的错误消息(可能被截断)格式化到给定的缓冲区中。
使用 C++,您可以从错误报告机制返回(引用)一个标准字符串;这意味着您可以格式化字符串以包含文件名或其他动态属性。收集信息的代码将负责释放字符串,由于 C++ 具有析构函数,这不是(不应该)成为问题。您可能仍想使用
mmap()
加载消息的格式字符串。您确实需要小心加载的文件,特别是用作格式字符串的任何字符串。 (此外,如果您正在处理 I18N/L10N,您需要担心是否使用 '
n$
表示法来允许参数重新排序;并且您必须担心不同文化的不同规则/关于句子单词出现顺序的语言。)In C, if you don't have internationalization (I18N) or localization (L10N) to worry about, then pointers to constant data is a good way to supply error message strings. However, you often find that the error messages need some supporting information (such as the name of the file that could not be opened), which cannot really be handled by constant data.
With I18N/L10N to worry about, I'd recommend storing the fixed message strings for each language in an appropriately formatted file, and then using
mmap()
to 'read' the file into memory before you fork any threads. The area so mapped should then be treated as read-only (usePROT_READ
in the call tommap()
).This avoids complicated issues of memory management and avoids memory leaks.
Consider whether to provide a function that can be called to get the latest error. It can have a prototype such as:
I'm assuming that the error number is returned by some other function call; the library function then consults any threadsafe memory it has about the current thread and the last error condition returned to that thread, and formats an appropriate error message (possibly truncated) into the given buffer.
With C++, you can return (a reference to) a standard String from the error reporting mechanism; this means you can format the string to include the file name or other dynamic attributes. The code that collects the information will be responsible for releasing the string, which isn't (shouldn't be) a problem because of the destructors that C++ has. You might still want to use
mmap()
to load the format strings for the messags.You do need to be careful about the files you load and, in particular, any strings used as format strings. (Also, if you are dealing with I18N/L10N, you need to worry about whether to use the '
n$
notation to allow for argument reordering; and you have to worry about different rules for different cultures/languages about the order in which the words of a sentence are presented.)我猜你可以像 Windows 一样使用 PWideChars。它的线程安全。您需要的是调用应用程序创建一个 PwideChar,Dll 将使用它来设置错误。然后,调用应用程序需要读取该 PWideChar 并释放其内存。
I guess you could use PWideChars, as Windows does. Its thread safe. What you need is that the calling app creates a PwideChar that the Dll will use to set an error. Then, the callling app needs to read that PWideChar and free its memory.
R. 有一个很好的答案(使用 static const char []),但是如果您要使用各种口语,我喜欢使用 Enum 来定义错误代码。这比一些 #define 将一堆名称定义为一个 int 值要好。
R. has a good answer (use static const char []), but if you are going to have various spoken languages, I like to use an Enum to define the error codes. That is better than some #define of a bunch of names to an int value.
return -ENOENT;
风格。errno
— even if it is potentially TLSed by an implementation); aking to Linux kernel's style ofreturn -ENOENT;
.strerror
that takes such an integer and returns a pointer to a const string. This function can transparently do I18N if needed, too, as gettext-returnable strings also remain constant over the lifetime of the translation database.如果您需要提供非静态错误消息,那么我建议返回如下字符串:error_code_t function(, char** err_msg)。然后提供一个函数来释放错误消息:void free_error_message(char* err_msg)。通过这种方式,您可以隐藏错误字符串的分配和释放方式。当然,只有错误字符串本质上是动态的才值得实现,这意味着它们传达的不仅仅是错误代码的翻译。
请注意 mu 格式。我是用手机写的...
If you need to provide non-static error messages, then I recommend returning strings like this: error_code_t function(, char** err_msg). Then provide a function to free the error message: void free_error_message(char* err_msg). This way you hide how the error strings are allocated and freed. This is of course only worth implementing of your error strings are dynamic in nature, meaning that they convey more than just a translation of error codes.
Please havy oversight with mu formatting. I'm writing this on a cell phone...