【问题标题】:Within a DLL function returning memory, where to allocate and where to deallocate?在返回内存的 DLL 函数中,在哪里分配和在哪里释放?
【发布时间】:2021-07-20 15:58:11
【问题描述】:

我对 DLL 的编写/使用非常陌生,并且在 DLL 中编写了一个函数,该函数接受一个字符串,并将另一个字符串作为输出返回给可执行文件。

#define DECL_EXPORT  extern "C" __declspec(dllexport) 

DECL_EXPORT char * organizeArgs(const char * args) {
    uint32 outputLen;
    ... 
    char * result = new char[outputLen];
    ...
    return result;
}

对我来说最简单的方法是在 DLL 中分配内存并将其返回给可执行文件以解除分配,但我读到这通常很糟糕,因为它会中断,当 dll 和 exe 之间的分配代码是不相同。试图避免在 dll 中分配和在可执行文件中解除分配会使这个函数及其使用方式变得更加复杂。

应该如何处理这种类型的分配/解除分配?

我有几个理论,但他们都觉得有点可怕:

  • 在单独的 dll 函数调用中计算输出的大小,因此调用者可以为输出分配那么多内存并将其提供给 dll。这似乎是最明智的解决方案,但需要运行两次分析代码:
DECL_EXPORT uint32 organizeArgsSize(const char * args) { 
    ...
    return size; 
}

DECL_EXPORT char * organizeArgs(const char * args, char * outputBuffer) { 
    ...
    return outputBuffer;
}
  • 在 dll 上分配并返回一个指针,该指针不希望可执行文件释放,该指针在被 dll 函数的第二次调用覆盖之前是有效的。我认为这对调用者来说具有最佳的人体工程学设计,但由于我是多线程的,因此需要thread_local 存储。我仍在尝试研究在 dll 中使用 thread_local 是否可以接受并且没有发现任何东西:
DECL_EXPORT const char * organizeArgs(const char * args) { 
    thread_local std::string buffer;
    ...
    return buffer.c_str();
}

我想这样的事情在 DLL 编写中经常出现,而且我认为它比实际更难。一般是怎么做的?

【问题讨论】:

  • 一个函数获取资源,一个函数释放它。
  • MS 提供了一组用于跨(潜在)ABI 边界分配和释放内存的函数,请参阅docs.microsoft.com/en-us/windows/win32/memory/… 您需要告诉分配内存的函数的用户使用什么函数来释放它。
  • @Someprogrammerdude 我已经准备好避免拥有我无法删除的堆 [] 我犯了罪hastebin.com/uzeqivivug.cpp
  • FYI -- 如果这是 DLL 应该被其他语言使用,那么您使用的数据类型应该是 Windows 普遍识别的数据类型,即LONG、LPCSTR 等.

标签: c++ memory-management dll


【解决方案1】:

你错过了一个:在 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 自己分配内存,您可以使用freeaddrinfo 释放它。 (选项 3)
  • asctime 返回指向线程局部或全局缓冲区的指针(选项 2)。 (我希望它是线程本地的,但 MSDN 并不清楚!)

【讨论】:

    【解决方案2】:

    另一种方法是将函数更改为以下内容:

    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并获取返回值,第二次outbuf 是分配的缓冲区。

    这正是一些 Windows API 函数的工作原理。 DLL 不分配任何内存。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-27
      • 2016-02-28
      • 2013-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多