在返回内存的 DLL 函数中,在哪里分配和在哪里释放?
Within a DLL function returning memory, where to allocate and where to deallocate?
我对 DLL writing/usage 非常陌生,并且在 DLL 中编写了一个接受字符串的函数,return将另一个字符串作为输出发送到可执行文件。
#define DECL_EXPORT extern "C" __declspec(dllexport)
DECL_EXPORT char * organizeArgs(const char * args) {
uint32 outputLen;
...
char * result = new char[outputLen];
...
return result;
}
对我来说最简单的方法是在 DLL 中分配内存,然后 return 将其分配给可执行文件以解除分配,但我读到这通常很糟糕,因为它会在分配时中断dll 和 exe 之间的代码不相同。试图避免在 dll 中分配和在可执行文件中取消分配会使此功能及其使用方式变得更加复杂。
这种allocation/deallocation应该如何处理?
我有几个理论,但都觉得有点糟糕:
- 在单独的 dll 函数调用中计算输出的大小,以便调用者可以为输出分配那么多的内存并将其提供给 dll。这似乎是最明智的解决方案,但需要 运行 两次分析代码:
DECL_EXPORT uint32 organizeArgsSize(const char * args) {
...
return size;
}
DECL_EXPORT char * organizeArgs(const char * args, char * outputBuffer) {
...
return outputBuffer;
}
- 在 dll 和 return 上分配一个可执行文件不希望释放的指针,该指针在被 dll 函数的第二次调用覆盖之前一直有效。我认为这对调用者来说是最符合人体工程学的,但由于我是多线程的,这将需要
thread_local
存储空间。我仍在尝试研究在 dll 中使用 thread_local
是否可以接受并且没有发现任何东西:
DECL_EXPORT const char * organizeArgs(const char * args) {
thread_local std::string buffer;
...
return buffer.c_str();
}
我想像这样的事情在 DLL 编写中经常出现,而且我发现它比实际情况更难。通常是怎么做的?
你错过了一个:在 DLL 中放置一个释放函数:
DECL_EXPORT char * organizeArgs(const char * args) {
...
char * result = new char[outputLen];
...
return result;
}
DECL_EXPORT void organizeArgsDeallocate(char *organizedArgs) {
delete [] organizedArgs;
}
这没关系,因为 new
和 delete
运算符是在同一个 DLL 中调用的。
还有第四种方法:使用在每个 DLL 中没有不同的分配方法。问题首先出现是因为每个 DLL 可能使用不同的 MSVCRT DLL(C/C++ 标准库)。但它们都有相同的 Win32 API DLL,并且您可以共享从 Win32 API 函数分配的内存。
DECL_EXPORT char * organizeArgs(const char * args) {
...
char * result = (char*)HeapAlloc(GetProcessHeap(), 0, outputLen);
// don't forget to check for NULL return value meaning out-of-memory
// or you can pass the HEAP_GENERATE_EXCEPTIONS flag
...
return result;
}
// caller does HeapFree(GetProcessHeap(), 0, result)
请注意,在 Linux 上,您通常 可以 在不同的共享库上调用 malloc
和 free
,因为它们是共享的 - 您不需要不需要使用任何这些解决方法。
这些都是解决这个问题的有效方法,事实上,您可以在标准库和 Win32 中找到所有这些方法 API:
FormatMessage
允许您使用 NULL 缓冲区调用它来计算大小(选项 1) 或 您可以传递某个标志,它将分配缓冲区一种跨 DLL 安全的方式(选项 4)。
getaddrinfo
allocates memory itself and you can free it with freeaddrinfo
。 (选项 3)
asctime
returns 指向线程本地或全局缓冲区的指针(选项 2)。 (我希望它是线程本地的,但 MSDN 不清楚!)
另一种方法是将您的函数更改为以下内容:
DECL_EXPORT LONG organizeArgs(LPCSTR args, LPSTR outbuf, LONG length);
那么 API 可以这样记录:
args - is the set of arguments
outbuf - is the output buffer or NULL
length - length of the output buffer, ignored if outbuf is NULL
Returns:
Number of characters written to outbuf, or if outbuf is NULL,
returns the maximum number of characters that would have been written.
因此是否调用该函数两次是客户端的责任。如果客户确信他们有足够大的缓冲区来保存信息,那么他们将分配它并使用 length
参数调用您的函数一次以限制字符数。
如果他们没有信心或者想确保他们得到所有的arg信息,那么客户端负责调用你的函数两次,第一次outbuf
为NULL并得到return 值,第二次 outbuf
是分配的缓冲区。
这正是一些 Windows API 函数的工作原理。 DLL 不分配任何内存。
我对 DLL writing/usage 非常陌生,并且在 DLL 中编写了一个接受字符串的函数,return将另一个字符串作为输出发送到可执行文件。
#define DECL_EXPORT extern "C" __declspec(dllexport)
DECL_EXPORT char * organizeArgs(const char * args) {
uint32 outputLen;
...
char * result = new char[outputLen];
...
return result;
}
对我来说最简单的方法是在 DLL 中分配内存,然后 return 将其分配给可执行文件以解除分配,但我读到这通常很糟糕,因为它会在分配时中断dll 和 exe 之间的代码不相同。试图避免在 dll 中分配和在可执行文件中取消分配会使此功能及其使用方式变得更加复杂。
这种allocation/deallocation应该如何处理?
我有几个理论,但都觉得有点糟糕:
- 在单独的 dll 函数调用中计算输出的大小,以便调用者可以为输出分配那么多的内存并将其提供给 dll。这似乎是最明智的解决方案,但需要 运行 两次分析代码:
DECL_EXPORT uint32 organizeArgsSize(const char * args) {
...
return size;
}
DECL_EXPORT char * organizeArgs(const char * args, char * outputBuffer) {
...
return outputBuffer;
}
- 在 dll 和 return 上分配一个可执行文件不希望释放的指针,该指针在被 dll 函数的第二次调用覆盖之前一直有效。我认为这对调用者来说是最符合人体工程学的,但由于我是多线程的,这将需要
thread_local
存储空间。我仍在尝试研究在 dll 中使用thread_local
是否可以接受并且没有发现任何东西:
DECL_EXPORT const char * organizeArgs(const char * args) {
thread_local std::string buffer;
...
return buffer.c_str();
}
我想像这样的事情在 DLL 编写中经常出现,而且我发现它比实际情况更难。通常是怎么做的?
你错过了一个:在 DLL 中放置一个释放函数:
DECL_EXPORT char * organizeArgs(const char * args) {
...
char * result = new char[outputLen];
...
return result;
}
DECL_EXPORT void organizeArgsDeallocate(char *organizedArgs) {
delete [] organizedArgs;
}
这没关系,因为 new
和 delete
运算符是在同一个 DLL 中调用的。
还有第四种方法:使用在每个 DLL 中没有不同的分配方法。问题首先出现是因为每个 DLL 可能使用不同的 MSVCRT DLL(C/C++ 标准库)。但它们都有相同的 Win32 API DLL,并且您可以共享从 Win32 API 函数分配的内存。
DECL_EXPORT char * organizeArgs(const char * args) {
...
char * result = (char*)HeapAlloc(GetProcessHeap(), 0, outputLen);
// don't forget to check for NULL return value meaning out-of-memory
// or you can pass the HEAP_GENERATE_EXCEPTIONS flag
...
return result;
}
// caller does HeapFree(GetProcessHeap(), 0, result)
请注意,在 Linux 上,您通常 可以 在不同的共享库上调用 malloc
和 free
,因为它们是共享的 - 您不需要不需要使用任何这些解决方法。
这些都是解决这个问题的有效方法,事实上,您可以在标准库和 Win32 中找到所有这些方法 API:
FormatMessage
允许您使用 NULL 缓冲区调用它来计算大小(选项 1) 或 您可以传递某个标志,它将分配缓冲区一种跨 DLL 安全的方式(选项 4)。getaddrinfo
allocates memory itself and you can free it withfreeaddrinfo
。 (选项 3)asctime
returns 指向线程本地或全局缓冲区的指针(选项 2)。 (我希望它是线程本地的,但 MSDN 不清楚!)
另一种方法是将您的函数更改为以下内容:
DECL_EXPORT LONG organizeArgs(LPCSTR args, LPSTR outbuf, LONG length);
那么 API 可以这样记录:
args - is the set of arguments
outbuf - is the output buffer or NULL
length - length of the output buffer, ignored if outbuf is NULL
Returns:
Number of characters written to outbuf, or if outbuf is NULL,
returns the maximum number of characters that would have been written.
因此是否调用该函数两次是客户端的责任。如果客户确信他们有足够大的缓冲区来保存信息,那么他们将分配它并使用 length
参数调用您的函数一次以限制字符数。
如果他们没有信心或者想确保他们得到所有的arg信息,那么客户端负责调用你的函数两次,第一次outbuf
为NULL并得到return 值,第二次 outbuf
是分配的缓冲区。
这正是一些 Windows API 函数的工作原理。 DLL 不分配任何内存。