【问题标题】:Passing data between dll boundaries in c++ Safely在 C++ 中安全地在 dll 边界之间传递数据
【发布时间】:2017-05-13 07:30:13
【问题描述】:

假设有一个结构

struct MyDataStructure
{
    int a;
    int b;
    string c;
};

让一个dll暴露的接口中有一个函数。

class IDllInterface
{
    public:
       void getData(MyDataStructure&) = 0;
};

从加载 dll 的客户端 exe 中,以下代码是否安全?

...
IDllInterface* dll = DllFactory::getInterface(); // Imagine this exists
MyDataStructure data;
dll->getData(data);
...

当然,假设客户端和 dll 都知道 MyDataStructure。同样根据我的理解,由于代码是针对 dll 和 exe 分别编译的,因此对于不同的编译器/编译器版本,MyDataStructure 可能会有所不同。我的理解是否正确。

如果是这样,当使用不同的编译器/编译器版本时,如何在 dll 边界之间安全地传递数据。

【问题讨论】:

  • “我的理解正确吗。”是的。
  • 没关系,这基本上就是进程中COM的工作方式。
  • 啊。在我将其作为stackoverflow.com/questions/5661738/… 的副本关闭之前,您只是 获得了那个赏金集。
  • 您的MyDataStructure 包含一个std::string 对象,因此它与建议的副本完全相同。
  • 如果你想要跨不同编译器的可移植性,你必须抛弃任何 C++ 库类型,只依赖已知大小的类型(int16_t 和 int32_t,而不是 short 和 int)。如果你只针对这个平台,你可能会得到 Windows API 提供的一些好处。

标签: c++ c dll cross-compiling


【解决方案1】:

您可以使用“协议”方法。为此,您可以使用内存缓冲区来传输数据,双方只需就缓冲区布局达成一致。

协议协议可能类似于:

  1. 我们不使用结构,我们只使用内存缓冲区 - (给我一个指针或任何工具包允许共享内存缓冲区的方法。
  2. 在设置任何数据之前,我们将缓冲区清零。
  3. 所有整数在缓冲区中使用 4 个字节。这意味着每一方都使用其编译器下的任何 int 类型为 4 个字节,例如整数/长。
  4. 对于两个整数的特殊情况,前 8 个字节是整数,之后是字符串数据。

    #define MAX_STRING_SIZE_I_NEED 128

    // 8 bytes for ints.

    #define DATA_SIZE (MAX_STRING_SIZE_I_NEED + 8)

    char xferBuf[DATA_SIZE];

所以 Dll 设置 int 等。例如

void GetData(void* p);

// "int" is whatever type is known to use 4 bytes

(int*) p = intA_ValueImSending;
(int*) (p + 4) = intB_ValueImSending;
strcpy((char*) (p + 8), stringBuf_ImSending);

在接收端,很容易将缓冲的值放入结构中:

char buf[DATA_SIZE];
void* p =(void*) buf;
theDll.GetData(p);
theStrcuctInstance.intA = *(int*) p;
theStrcuctInstance.intB = *(int*) (p + 4);
...

如果您愿意,您甚至可以就每个整数字节的字节顺序达成一致,并设置缓冲区中每个整数的 4 个字节中的每一个 - 但您可能不需要达到那种程度。

为了更通用的目的,双方可以就缓冲区中的“标记”达成一致。缓冲区如下所示:

<marker>
<data>
<marker>
<data>
<marker>
<data>
...

标记:第1个字节表示数据类型,第2个字节表示长度(很像网络协议)。

【讨论】:

  • 我明白你的意思。在最低级别交换数据,并将其放在所需的结构中。类似于二进制文件之间的序列化和反序列化数据。接受你的回答:-)
  • 有没有我可以使用的库来代替自己做这个?
  • 因为正如您所指出的,它有点低级,所以库可能不太有用,因为它们需要知道您的编译器环境等,每个 int 有多少字节,int 如何布局等。如果您想使用“内置”的东西来使这更容易,那么“协议”可以制定一个规则,即所有整数都作为字符串表示形式传输。因此,您可以 "sprintf(p, "%x", myIntValue)" 将整数放入发送端的缓冲区中,并使用 atoi() 或类似方法转换回整数。事实上,这可能是一种很好的技术,因为它绕过了每一边表示整数的方式。
【解决方案2】:

如果要在 COM 中传递字符串,通常需要使用 COM BSTR 对象。您可以使用SysAllocString 创建一个。这被定义为在编译器、版本、语言等之间保持中立。与流行的看法相反,COM 确实直接支持 int 类型——但从它的角度来看,int 始终是 32 位类型。如果你想要一个 64 位整数,那就是Hyper,用 COM 语言表示。

当然,您可以使用连接双方都知道/理解/同意的其他格式。除非您有非常充分的理由这样做,否则几乎可以肯定这是一个糟糕的主意。 COM 的主要优势之一就是您似乎想要的那种互操作性——但发明自己的字符串格式会大大限制这一点。

【讨论】:

    【解决方案3】:

    使用 JSON 进行通信。

    我想我找到了一种更简单的方法来回答我自己的问题。正如@Greg 的回答所建议的那样,必须确保数据表示遵循协议,例如网络协议。这确保了不同二进制组件(此处为 exe 和 dll)之间的对象表示变得无关紧要。如果我们再想一想,这和 JSON 通过定义一个简单的对象表示协议解决的问题是一样的。

    因此,根据我的说法,一个简单而强大的解决方案是从 exe 中的对象构造一个 JSON 对象,对其进行序列化,将其作为字节传递到 dll 边界,然后在 dll 中对其进行反序列化。 dll 和 exe 之间的唯一协议是两者都使用相同的字符串编码(例如 UTF-8)。

    https://en.wikibooks.org/wiki/JsonCpp

    可以使用上面的 Jsoncpp 库。字符串在 Jsoncpp 库中默认使用 UTF-8 编码,这样也很方便:-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-16
      • 2016-08-19
      • 2010-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-12
      相关资源
      最近更新 更多