在返回内存的 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应该如何处理?

我有几个理论,但都觉得有点糟糕:

DECL_EXPORT uint32 organizeArgsSize(const char * args) { 
    ...
    return size; 
}

DECL_EXPORT char * organizeArgs(const char * args, char * outputBuffer) { 
    ...
    return outputBuffer;
}
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;
}

这没关系,因为 newdelete 运算符是在同一个 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 上,您通常 可以 在不同的共享库上调用 mallocfree,因为它们是共享的 - 您不需要不需要使用任何这些解决方法。


这些都是解决这个问题的有效方法,事实上,您可以在标准库和 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 不分配任何内存。