当签名包含 BYTE 时在 C# 中使用 C++ DLL**
Posted
技术标签:
【中文标题】当签名包含 BYTE 时在 C# 中使用 C++ DLL**【英文标题】:Using a C++ DLL in C# when signature includes BYTE** 【发布时间】:2017-02-13 14:40:44 【问题描述】:我正在开发一个 C# 项目,并且正在包装一个 C++ DLL 以在项目中使用。我在一个测试项目中捕捉到了这种行为,并重命名了函数调用以保护无辜者。
一切似乎都很好,除了一种我很难理解的功能。 DLL 标头中该函数的签名是:
int32_t DoTheWork(BYTE **dest, BYTE *Src, int32_t szSrc);
我的包装器毫无问题地接收到 Src 字节数组(很容易测试,因为这只是一个 char 字符串)。 return dest 参数不是那么简单。
我尝试了不同的方法将 dest 参数从 C# 传递给包装函数,但是当我收到它返回时,C# 中的 dest 字节数组的长度为 1(而不是预期的 32)字节,或者返回崩溃。我在下面的实例是崩溃。我需要了解如何将字节数组作为参考传递,将结果复制到该字节数组中,并以完整的字节补码返回它而不会崩溃。我花了一天多的时间在网上查找并更改代码,但仍然无法正常工作。
另外,将在 C++ DLL 中创建的指针一直带到 C# 调用函数中,而不是将值复制到我的 C++ 包装器中的 C# 字节数组中,对我来说会更好吗?如果是这样,我该如何正确地清理 C# 中的内存?
我在 Win8 上使用 VS2010。这是我的代码:
** OriginalCPPClass.h for OriginalCPPDll.dll
class OriginalCPPClass
public:
OriginalCPPDLLClass();
virtual ~OriginalCPPDLLClass();
int32_t DoTheWork(BYTE **dest, BYTE *Src, int32_t szSrc);
;
** WrapperDLL.cpp(没有随附的 .h 文件)
#include "CytoCrypto.h"
extern "C"
#define WRAPPERCLASS_EXPORT __declspec(dllexport)
WRAPPERCLASS_EXPORT OriginalCPPClass* Wrap_Create()
return new OriginalCPPClass();
WRAPPERCLASS_EXPORT void Wrap_Destroy(OriginalCPPClass* pObj)
delete pObj;
WRAPPERCLASS_EXPORT int32_t __cdecl Wrap_DoTheWork(OriginalCPPClass* pObj, BYTE **pDest, BYTE *pSrc, int32_t szSrc)
BYTE *result = NULL;
int32_t sz = pObj->DoTheWork(&result, pSrc, szSrc);
*(result+sz) = '\0';
if (sz > 0)
memcpy(pDest, result, sz );
return (sz >= 0) ? sz : 0;
** 程序.cs
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
class Program
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrap_Create();
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Wrap_Destroy(IntPtr pObj);
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 Wrap_DoTheWork(IntPtr pObj, out IntPtr pDest, byte[] src, Int32 szSrc);
static void Main(string[] args)
string src = "this is the source string";
IntPtr pnt = Marshal.AllocHGlobal(1000);
byte[] bytearray = new byte[1000];
byte[] srcBytes = Encoding.ASCII.GetBytes(src);
Int32 szSrc = srcBytes.Length;
IntPtr obj = Wrap_Create();
Int32 size = Wrap_DoTheWork(obj, out pnt, srcBytes, szSrc);
Marshal.Copy(pnt, bytearray, 0, size);
Wrap_Destroy(obj);
Marshal.Copy(pnt, bytearray, 0, size);
错误对话框显示:
在 mscorlib.dll 中发生了“System.AccessViolationException”类型的未处理异常 附加信息:试图读取或写入受保护的内存。这通常表明其他内存已损坏。
【问题讨论】:
BYTE **dest
在这种情况下是指向字节数组的指针。被调用者负责分配数组,并将该数组的地址返回给调用者。这希望在您的 C# 代码中成为 out IntPtr
。然后,您将使用 Marshal
类从该指针复制到 C# 数组。非托管代码也需要导出一个释放器。
这个函数很难可靠地调用,它想要返回一个数组,但是没有指导你应该如何释放数组的内存。如果您不应该释放它,那么写入缓冲区是非常危险的。而且您没有正确包装它, pDest 需要是 BYTE* 以便调用者可以传递要填充的缓冲区。你需要一个额外的参数来说明缓冲区有多大,这样你就不能写超出它并破坏内存。与编写 OriginalCPPClass 的程序员交谈,他需要做得更好或提供指导。
那么我需要做一个 out IntPtr, Marshal.Copy 到 bytearray 变量,然后调用 C++ 包装器来做删除?我会再试一次。我之前尝试过,但是做了 BYTE* 的事情,而不是 BYTE**。这可能是问题所在,因为 C# 不知道这种类型的构造是有道理的。谢谢。我可能会因此崩溃,这是我上次尝试这种方式时记得的,但将签名更改为 BYTE* 可能会有所帮助。
我现在似乎可以工作,但我没有在 C# 端获得正确的值。我在包装函数中放入了一个循环,以向我显示我应该返回的字符串的无符号字符(转换为 uint)值。它没有显示很多重复。当我查看返回的字节数组时,它是一个完全重复的 4 个值序列,看起来不像包装函数中的任何东西。我想知道我是否应该调用 Marshal.AllocHGlobal(1000) 来设置 pnt 指针,但如果我不这样做,我会在退出调用的路上崩溃。
【参考方案1】:
我终于找到了做我需要的正确方法。事实证明,只传递一个预先确定大小的字节数组来收集结果是好的(不幸的是,必须传递一个足够大的缓冲区来处理结果,其长度提前未知,但永远不会大于原始大小的两倍)。然后在包装类中,当我调用原始 C++ 库时,我会收到一个新分配的内存块,并将内容复制到我的“BYTE* dest”参数中(不再作为 BYTE** 传递),并删除从图书馆。我只是将其留给自动编组来处理数组在两个方向上的传输。完美运行,我得到的字节串被证明是正确的。感谢大家的帮助。
这是我的最终代码:
** OriginalCPPClass.h for OriginalCPPDll.dll
class OriginalCPPClass
public:
OriginalCPPDLLClass();
virtual ~OriginalCPPDLLClass();
int32_t DoTheWork(BYTE **dest, BYTE *Src, int32_t szSrc);
;
** WrapperDLL.cpp
#include "CytoCrypto.h"
extern "C"
#define WRAPPERCLASS_EXPORT __declspec(dllexport)
WRAPPERCLASS_EXPORT OriginalCPPClass* Wrap_Create()
return new OriginalCPPClass();
WRAPPERCLASS_EXPORT void Wrap_DestroyPtr(BYTE* ptr)
HeapFree(GetProcessHeap(), 0, ptr);
WRAPPERCLASS_EXPORT int32_t __cdecl Wrap_DoTheWork(OriginalCPPClass* pObj, BYTE *pDest, BYTE *pSrc, int32_t szSrc)
BYTE *result = NULL;
int32_t sz = pObj->DoTheWork(&result, pSrc, szSrc);
if (sz > 0)
memcpy(pDest, result, ret+1);
if (result)
pObj->DestroyPtr(result);
return (sz >= 0) ? sz : 0;
** 程序.cs
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
class Program
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrap_Create();
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Wrap_Destroy(IntPtr pObj);
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 Wrap_DoTheWork(IntPtr pObj, byte[] dest, byte[] src, Int32 szSrc);
static void Main(string[] args)
string srcStr = "this is the source string";
byte[] resBytes = new byte[srcStr.Length*2];
byte[] srcBytes = Encoding.ASCII.GetBytes(srcStr);
Int32 srcSize = srcBytes.Length;
IntPtr obj = Wrap_Create();
Int32 size = Wrap_DoTheWork(obj, resBytes, srcBytes, srcSize);
Wrap_Destroy(obj);
【讨论】:
以上是关于当签名包含 BYTE 时在 C# 中使用 C++ DLL**的主要内容,如果未能解决你的问题,请参考以下文章
C# 如何验证来自电子邮件的数字签名(编码 SeveBit)
验证openssl c ++中的签名,该签名由JAVA DSA签名?