DLL共享数据的推荐方式是什么?

时间:2018-09-16 08:54:55

标签: c++ windows winapi dll shared-memory

流量

我有一个用C++编写的读取器DLL。
我还用某种语言(不是用C++编写了作家DLL。
DLL在同一进程中同步运行。

  1. 读者DLL调用作家的DLL API GetData
  2. Writer DLL通过下载,提取等来准备数据。
  3. 读取器DLL读取并使用数据

问题

DLL共享数据的推荐方式是什么?


方法1

Reader DLL将文件路径参数传递给Writer DLL,并从文件中读取数据。

缺点

我想避免将数据写入磁盘。即使这是最可靠的解决方案,我也想探索不同的选择,因为当您不需要磁盘上的数据时,将数据写到磁盘上对我来说似乎不是很优雅。


方法2

写入器DLL将在堆上分配缓冲区,并向读取器DLL返回地址和大小。

缺点

读取器DLL必须释放内存。可行吗按地址和大小删除内存?

此外,这可能是跨模块/语言分配和释放缓冲区的大问题。


方法3

将GetData()分为两个调用。

  1. 读取器DLL调用GetDataSize()
  2. Reader DLL分配缓冲区并将地址传递给Writer DLL
  3. 写入器DLL填充缓冲区
  4. 读取器DLL使用缓冲区
  5. 读取器DLL释放缓冲区

这是可接受的WINAPI方法。

缺点

我认为Writer DLL能够在写入之前知道数据的大小,但并非总是如此。


方法4

使用Windows文件映射

缺点

与方法2和方法3类似。

  • 谁将创建文件映射?
  • 谁将取消映射?
  • 文件映射没有动态大小。创建尺寸时必须定义尺寸。

2 个答案:

答案 0 :(得分:1)

所有DLL都在相同的进程和地址空间中运行。因此,他们可以直接在内存中共享任何数据。挑战仅在于如何授予对数据的访问权限,尤其是在使用其他语言的情况下。

  • 选项1很简单,因为您只需要将公用文件名传递给阅读器即可。但是为什么要这样开销呢? 字符串变体:如果您设法将文件名作为字符串传递,则还可以让编写者序列化字符串中的数据并将其传递给读取器

  • 选项2更精致。如果在DLL的同一侧分配/取消分配内存,或者使用Windows API分配缓冲区,则可以。否则,它可能很棘手,因为memory allocation passes the DLL barriers with difficulty(不是因为地址空间,而是因为使用不同堆和不同分配/释放例程的风险)。此外,您不能确定调用程序可以正确管理C ++对象的生命周期(如果您在C ++方面使用某些RAII设计)。因此,只有在读者管理缓冲区生命周期的情况下才可以使用此选项:

    • 呼叫者要求读取器进行分配,然后呼叫者向写入器提供缓冲区的地址,然后呼叫者再次呼叫读取器以处理数据并释放缓冲区。
    • 固定的缓冲区大小是可以接受的,即daa的大小是已知的。
  • 选项3是选项2做得很好

  • 如果使用mapped file I/O,选项4仍然有磁盘开销,还有一个额外的问题:您可以在同一过程中将同一文件映射两次吗?如果您被这个选项所吸引,请再看一下我为上面的选项1提出的基于字符串的变体:共享字符串扮演了内存映射的角色,而没有文件的不便。

字符串变体似乎是跳过具有复杂数据结构的语言障碍的简便替代方法。几乎每种语言都有字符串。生产者可以构建其字符串,而不必事先知道数据的大小。最后,即使跨语言对字符串进行不同的管理,总会有way to access them in read-only

但是我的首选变体是组织整个事情,以便编写器(或充当中介的主程序)根据需要调用读取器的处理功能(当部分数据可用时),以明确定义类型的参数以进行函数调用。

答案 1 :(得分:1)

注意:在我们谈论两种不同语言之间传递数据时,我假设我们正在谈论“原始”数据(原始类型,POD和co。)。不需要任何特殊的销毁处理;如果不是这种情况,请在评论中告诉我。

  1. 显然可行,但除非绝望,否则我不会考虑。这两个dll位于相同的虚拟地址空间中,因此它们可以直接在内存中共享数据,而无需通过磁盘。

  2. 可行且常规地完成;通常必须解决的问题是,给定模块的“默认”堆通常是private 1 ,因此从一个分配和从另一个释放是很大的禁忌。有两种典型的实现方法:

    • 浏览肯定对两个模块都可用的堆;在Win32中,您经常会发现LocalAlloc / LocalFree(或其他Win32 API提供的堆原语)用于此目的,因为它们在逻辑上“位于”所有用户模式代码的下方,并提供了共享的堆可用于当前进程中的所有模块;因此,一方知道必须使用LocalAlloc进行分配,另一方知道必须使用LocalFree进行释放。一切正常;
    • 分配模块还为其分配的内存提供释放功能;客户端代码知道必须使用A函数来释放模块A_free()分配的所有内容。反过来,这可能只会包装您的语言解除分配功能,以用作您在“业务逻辑”导出功能中所做的分配的对应物。顺便说一句,最好有一个A_malloc()来标记预期由A_free()释放的分配-即使它们可能是简单的malloc / {{1 }},今天,您可能有兴趣在以后进行更改。
  3. 也经常进行;在Win32 API中,通常有一些特殊的调用形式,该形式允许检索需要分配的大小。如果无法轻松地计算出这样的大小而没有实际尝试执行该函数必须做的事情,或者如果这种大小发生波动(使用Win32 API检索进程数据,您可能不得不循环保存),则使用或实现可能会很麻烦如果在一个调用和另一个调用之间要检索的数据实际上在增加,则增加分配。

  4. 可以完成,尽管我从未见过针对进程内数据完成此操作。分配的开销将大于任何“常规”堆函数,但是没有什么比写入文件更重要。总的来说,它比free / LocalAlloc解决方案更麻烦,没有特别的好处,所以我不会打扰。

就个人而言,我会选择选项2-实现起来很简单,不需要对通常用C语言编写这些东西的方式进行大的更改-唯一的区别是您必须使用一对特定的分配/处理此数据时,释放功能起作用。

想到的另一种可能性是让您的函数将分配函数作为回调参数(如果算法需要,还可以考虑分配函数参数-想到动态增长的数组);它将是提供它的调用方,因此被调用的DLL将分配给调用方最喜欢的任何堆。


注释

  1. 尽管它可以被共享,例如如果两个模块针对同一C运行时动态链接,则可能是; OTOH,如果两个模块用不同的语言编写,则可能性很小。