在C++中跨DLL边界传递数据的安全方法

4

让我们来创建一个结构

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边界之间传递数据呢?


1
我的理解是正确的吗?是的。 - πάντα ῥεῖ
@Richard 不行,这很危险。数据结构可能会因为不同的编译器版本或者不同的编译器开关而有显著的差异。这就是为什么你必须根据 MSVC 的多线程/单线程或者调试/发布模型导入不同的 DLL 版本。 - πάντα ῥεῖ
1
啊,你在我关闭这个问题之前刚好设置了赏金,使得它无法成为 https://dev59.com/x2035IYBdhLWcg3wC7YR 的重复问题。 - Cody Gray
2
你的 MyDataStructure 包含一个 std::string 对象,因此它与提出的重复问题完全相同。 - Cody Gray
1
如果你想要在不同的编译器之间实现可移植性,那么你必须放弃任何 C++ 库类型,只依赖于已知大小的类型(例如 int16_t 和 int32_t 而不是 short 和 int)。如果你只针对 Windows 平台进行开发,那么可以利用 Windows API 提供的一些好处。 - Serge Ballesta
显示剩余5条评论
3个回答

3
您可以采用“协议”方法。为此,您可以使用内存缓冲区传输数据,双方只需就缓冲区布局达成一致即可。
协议协议可以是以下内容:
1. 我们不使用结构体,只使用内存缓冲区-(通过指针或任何工具包允许共享内存缓冲区的方式)。 2. 在设置任何数据之前,我们将缓冲区清零。 3. 所有的整数在缓冲区中使用4个字节。这意味着每一方都使用其编译器下4个字节的int类型,如int/long等。 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>
...

标记:第一个字节表示数据类型,第二个字节表示长度(非常类似于网络协议)。


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

1
如果您想在COM中传递字符串,通常需要使用COM BSTR对象。您可以使用SysAllocString创建一个BSTR对象,该函数被定义为在编译器、版本、语言等方面中立。与普遍的观点相反,COM确实直接支持int类型--但从其角度来看,int始终是32位类型。如果您需要64位整数,则在COM术语中称为Hyper
当然,您也可以使用其他格式,只要连接双方都知道/理解/同意即可。除非您有一个极好的理由这样做,否则几乎肯定会是一个糟糕的主意。COM的主要优势之一正是您似乎想要的交互操作--但发明自己的字符串格式将大大限制这种优势。

1

使用JSON进行通信。

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

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

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

可以使用上述的Jsoncpp库。在Jsoncpp库中,默认情况下字符串是以UTF-8编码的,因此非常方便 :-)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接