简体   繁体   English

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

[英]What's a good, threadsafe, way to pass error strings back from a C shared library

I'm writing a C shared library for internal use (I'll be dlopen()'ing it to a c++ application, if that matters). 我正在编写一个供内部使用的C共享库(如果这很重要的话,我会将它dlopen()用于c ++应用程序)。 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. 共享库通过JNI模块加载(除此之外)一些Java代码,这意味着所有恶梦错误模式的方式都可以来自我需要在应用程序中智能处理的JVM。 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? 在这种情况下,是否存在用于传递错误字符串的习惯用法,或者我是否将映射错误粘贴到整数并使用printfs进行调试?

Thanks! 谢谢!

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. 他们没有错,只是我不得不在这个问题的另一个方面进行斗争。

  1. AC API needs to provide numeric error codes, so that the code using the API can take sensible measures to recover from errors when appropriate, and pass them along when not. AC API需要提供数字错误代码,以便使用API​​的代码可以采取合理的措施在适当的时候从错误中恢复,并在没有时传递它们。 The errno.h codes demonstrate a good categorization of errors; errno.h代码表明错误的良好分类; in fact, if you can reuse those codes (or just pass them along, eg if all your errors come ultimately from system calls), do so. 实际上,如果您可以重用这些代码(或者只是传递它们,例如,如果您的所有错误最终来自系统调用),请执行此操作。
    • Do not copy errno itself. 不要复制errno本身。 If possible, return error codes directly from functions that can fail. 如果可能,直接从可能失败的函数返回错误代码。 If that is not possible, have a GetLastError() method on your state object. 如果这不可能,请在状态对象上使用GetLastError()方法。 You have a state object, yes? 你有一个状态对象,是吗?
  2. If you have to invent your own codes (the errno.h codes don't cut it), provide a function analogous to strerror , that converts these codes to human-readable strings. 如果你必须发明自己的代码( errno.h代码不会删除它),请提供类似于strerror的函数,它将这些代码转换为人类可读的字符串。
    • It may or may not be appropriate to translate these strings. 翻译这些字符串可能适合也可能不适合。 If they're meant to be read only by developers, don't bother. 如果它们只是由开发人员阅读,请不要打扰。 But if you need to show them to the end user, then yeah, you need to translate them. 但是如果你需要向最终用户展示它们,那么是的,你需要翻译它们。
    • The untranslated version of these strings should indeed be just string constants, so you have no allocation headaches. 这些字符串的未翻译版本应该只是字符串常量,因此您没有分配问题。 However, do not waste time and effort coding your own translation infrastructure. 但是,不要浪费时间和精力来编写自己的翻译基础架构。 Use GNU gettext . 使用GNU gettext
  3. If your code is layered on top of another piece of code, it is vital that you provide direct access to all the error information and relevant context information that that code produces, and you make it easy for developers against your code to wrap up all that information in an error message for the end user. 如果你的代码是在另一段代码的上面一层,它是您提供给所有的错误信息和代码生成相关的上下文信息的直接访问是至关重要的 ,你很容易让开发商对你的代码包所有最终用户的错误消息中的信息。
    • For instance, if your library produces error codes of its own devising as a direct consequence of failing system calls, your state object needs methods that return the 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. 例如,如果您的库产生自己设计的错误代码,这是系统调用失败的直接后果,那么您的状态对象需要返回在系统调用失败后立即观察到的errno值的方法,即所涉及文件的名称(如果有的话) ),理想情况下也是系统调用自身的名称。 People get this wrong waaay too often -- for instance, SQLite, otherwise a well designed API, does not expose the errno 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". 人们经常得到这个错误的waaay - 例如,SQLite,否则是一个设计良好的API,不会暴露errno值或文件的名称,这使得很难区分“数据库上的文件权限是错误的”从“你的代码中有一个错误”。

EDIT: Addendum: common mistakes in this area include: 编辑:附录:该领域的常见错误包括:

  • Contorting your API (eg with use of out-parameters) so that functions that would naturally return some other value can return an error code. 汇总API(例如使用out参数),以便自然返回其他值的函数可以返回错误代码。
  • Not exposing enough detail for callers to be able to produce an error message that allows a knowledgeable human to fix the problem. 没有为呼叫者公开足够的细节,以便能够产生错误消息,允许知识渊博的人解决问题。 (This knowledgeable human may not be the end user. It may be that your error messages wind up in server log files or crash reports for developers' eyes only.) (这位知识渊博的人可能不是最终用户。可能是您的错误消息最终会出现在服务器日志文件或崩溃报告中,仅供开发人员使用。)
  • Exposing too many different fine distinctions among errors. 在错误中暴露出太多不同的细微区别。 If your callers will never plausibly do different things in response to two different error codes, they should be the same code. 如果您的呼叫者在响应两个不同的错误代码时永远不会合理地执行不同的操作,则它们应该是相同的代码。
  • Providing more than one success code. 提供多个成功代码。 This is asking for subtle bugs. 这要求微妙的错误。

Also, think very carefully about which APIs ought to be allowed to fail. 另外,请仔细考虑应该允许哪些API失败。 Here are some things that should never fail: 以下是一些永远不会失败的事情:

  • Read-only data accessors, especially those that return scalar quantities, most especially those that return Booleans. 只读数据访问器,特别是那些返回标量的数据访问器,尤其那些返回布尔值的数据访问器。
  • Destructors, in the most general sense. 最一般意义上的析构函数。 (This is a classic mistake in the UNIX kernel API: close and munmap should not be able to fail. Thankfully, at least _exit can't.) (这是UNIX内核API中的一个经典错误: closemunmap不应该失败。谢天谢地,至少_exit不能。)
  • There is a strong case that you should immediately call abort if malloc fails rather than trying to propagate it to your caller. 如果malloc失败而不是尝试将其传播给调用者,则应该立即调用abort (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.) (由于异常和RAII,在C ++中不是这样 - 如果你很幸运能够开发一个正确使用这两者的C ++项目。)

In closing: for an example of how to do just about everything wrong , look no further than XPCOM . 最后:关于如何解决所有错误的例子,请看XPCOM

You return pointers to static const char [] objects. 您返回指向static const char []对象的指针。 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. 如果需要将它们本地化,则返回指向只读内存映射本地化字符串的指针。

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. 在C中,如果您不需要担心国际化(I18N)或本地化(L10N),那么指向常量数据的指针是提供错误消息字符串的好方法。 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. 考虑到I18N / L10N,我建议将每种语言的固定消息字符串存储在适当格式的文件中,然后在分叉任何线程之前使用mmap()将文件“读取”到内存中。 The area so mapped should then be treated as read-only (use PROT_READ in the call to mmap() ). 这样映射的区域应该被视为只读(在调用mmap()使用PROT_READ )。

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: 它可以有一个原型,如:

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

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; 使用C ++,您可以从错误报告机制返回(引用)标准字符串; 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. 收集信息的代码将负责释放字符串,由于C ++具有析构函数,因此不会(不应该)成为问题。 You might still want to use mmap() to load the format strings for the messags. 您可能仍希望使用mmap()来加载messag的格式字符串。

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.) (此外,如果您正在处理I18N / L10N,您需要担心是否使用' n$表示法来允许参数重新排序;并且您必须担心不同文化/语言的不同规则有关提出一个句子的单词。)

I guess you could use PWideChars, as Windows does. 我想你可以像Windows一样使用PWideChars。 Its thread safe. 它的线程安全。 What you need is that the calling app creates a PwideChar that the Dll will use to set an error. 你需要的是调用应用程序创建一个PllChar,Dll将用它来设置错误。 Then, the callling app needs to read that PWideChar and free its memory. 然后,callling app需要读取该PWideChar并释放其内存。

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. R.有一个很好的答案(使用static const char []),但是如果你要使用各种口语,我喜欢使用Enum来定义错误代码。 That is better than some #define of a bunch of names to an int value. 这比一些名称中的某些#define要好一个int值。

  • return integers, don't set some global variable (like errno — even if it is potentially TLSed by an implementation); 返回整数,不要设置一些全局变量(比如errno - 即使它可能被实现TLS); aking to Linux kernel's style of return -ENOENT; aking到Linux内核的return -ENOENT;风格return -ENOENT; .
  • have a function similar to strerror that takes such an integer and returns a pointer to a const string. 有一个类似于strerror的函数,它接受一个这样的整数并返回一个指向const字符串的指针。 This function can transparently do I18N if needed, too, as gettext-returnable strings also remain constant over the lifetime of the translation database. 如果需要,此函数也可以透明地执行I18N,因为gettext-returnable字符串在翻译数据库的生命周期内也保持不变。

If you need to provide non-static error messages, then I recommend returning strings like this: error_code_t function(, char** err_msg). 如果您需要提供非静态错误消息,那么我建议返回如下字符串:error_code_t function(,char ** err_msg)。 Then provide a function to free the error message: void free_error_message(char* err_msg). 然后提供一个释放错误消息的函数: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. 请用mu格式来监督疏忽。 I'm writing this on a cell phone... 我在手机上写这个...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在C ++共享库中进行Threadsafe日志记录 - Threadsafe logging inside C++ Shared library 在 C++ 中将数据传递给线程的好方法是什么? - What's the good way to pass data to a thread in c++? 解决运行时库冲突的好方法是什么? - What's a good way to resolve Runtime library conflicts? 什么是矩阵运算的优秀C ++库 - What's a good C++ library for matrix operations 什么是最快的C ++类或C库,可将纬度和经度从十进制转换为字符串并返回 - What's the fastest C++ class or C library to convert latitude and longitude from decimal degrees to string and back C ++:从注入的DLL读取内存的好方法是什么? - C++ : What's a good way to read memory from injected DLL? 用C ++编写XML的好方法是什么? - What's a good way to write XML in C++? 在 c 中使用共享库很好,但在 c ++ 中使用相同的代码不好? - Use of shared library is good in c but same code is bad in c++? 在OSX上使用CMake将Adobe XMP工具包构建为共享库的最简单方法是什么? - What's the easiest way to build Adobe's XMP toolkit as a shared library with CMake on OSX? c++ 什么是从文本文件中读取的好方法? - c++ what would be a good way to read from text file?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM