C++ DLL 到 C# 错误:“试图读取或写入受保护的内存。这通常表明其他内存已损坏。”
Posted
技术标签:
【中文标题】C++ DLL 到 C# 错误:“试图读取或写入受保护的内存。这通常表明其他内存已损坏。”【英文标题】:C++ DLLs to C# error: "Attempted to read or write protected memory. This is often an indication that other memory is corrupt." 【发布时间】:2013-07-16 20:35:50 【问题描述】:我有 2 个 DLL。一个是 PrimaryDLL.dll,另一个是 DLLWrap.dll。 PrimaryDLL 有一个对象,一个名为 DiversifyKeyset 的函数,它在类构造函数中像这样贴花:
static PRIMARYDLL_API std::string DiversifyKeyset(std::string Key1, std::string Key2, std::string Key3);
这里是定义的方法:
std::string tKeyset::DiversifyKeyset(std::string Key1, std::string Key2, std::string Key3)
tKeyset* keyset = new tKeyset();
keyset ->Key1 = "12345678";
keyset ->Key2 = "23456789";
keyset ->Key3 = "34567890";
//return keyset --eventually I want to return this
return 0;
我的 DLLWrap.dll 在它的 .cpp 文件中调用这个函数:
cout << "Here is what is returned: " <<
firstDllLayer::tKeyset::DiversifyKeyset(a,b,c) << endl;
现在这一切都编译好了,库也建好了。当我调用 C# 代码调用第二个 DLLWrap.dll 时,我的错误出现了。我收到错误:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
所以我一直在绞尽脑汁想为什么这不起作用。我承受着很大的压力,但似乎找不到我收到此内存写入错误的原因。
所以为了帮助我提供我的代码。
我的 C++ PrimaryDLL.h 文件:
#pragma once
#include "stdafx.h"
#include <string>
#include <stdexcept>
extern "C"
#ifdef PRIMARYDLL_EXPORT
#define PRIMARYDLL_API __declspec(dllexport)
#else
#define PRIMARYDLL_API __declspec(dllimport)
#endif
namespace firstDllLayer
class tKeyset
public:
tKeyset();
std::string Key1;
std::string Key2;
std::string Key3;
tKeyset(std::string _key1, std::string _key2, std::string _key3);
static PRIMARYDLL_API std::string DiversifyKeyset(std::string Key1, std::string Key2, std::string Key3);
;
tKeyset::tKeyset()
Key1 = "";
Key2 = "";
Key3 = "";
tKeyset::tKeyset(std::string _Key1, std::string _Key2, std::string _Key3)
Key1 = _Key1;
Key2 = _Key2;
Key3 = _Key3;
这里是 PrimaryDll.cpp 文件:
// PrimaryDll.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "PrimaryDll.h"
#include <stdexcept>
#include <string>
using namespace std;
namespace firstDllLayer
tKeyset* MasterKeyset = new tKeyset("1234","5678","0910");
std::string tKeyset::DiversifyKeyset(std::string Key1, std::string Key2, std::string Key3)
tKeyset* keyset = new tKeyset();
keyset ->Key1 = "12345678";
keyset ->Key2 = "23456789";
keyset ->Key3 = "34567890";
//return Keyset; -eventually I would like to return this
return 0;
这是我的 DLLWrap.h 文件:
#pragma once
#include "stdafx.h"
#include "Primary.h"
#include <stdexcept>
#include <string>
extern "C"
#ifdef DLLWRAPDLL_EXPORT
#define DLLWRAP_API __declspec(dllexport)
#else
#define DLLWRAP_API __declspec(dllimport)
#endif
namespace MyFunc
class MyCall
public:
static DLLWRAP_API std::string DiversifyKeysetCall(std::string a,std::string b,std::string c);
;
DLLWrap.cpp 文件:
// DLLWrap.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "PrimaryDll.h"
#include <stdexcept>
#include "DLLWrap.h"
#include <iostream>
#include <string>
using namespace std;
namespace MyFunc
std::string MyCall::DiversifyKeysetCall(std::string a,std::string b,std::string c)
/*cout << "Here is what is returned: " <<
firstDllLayer::tKeyset::DiversifyKeyset(a,b,c) << endl;
return 0;*/
cout << "Here is what is returned: " <<
firstDllLayer::tKeyset::DiversifyKeyset(a,b,c) << endl;
return 0;
最后是 C# Program.cs 文件:
// Marshal.cs
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
public class DllHelper
[DllImport(@"C:\Users\user\Documents\Visual Studio 2010\Projects\KeyDll\Debug\DLLWrap.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?DiversifyKeysetCall@MyCall@MyFunc@@SA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V34@00@Z")]
public static extern string DiversifyKeysetCall(string a, string b, string c);
static void Main()
try
DllHelper.DiversifyKeysetCall("1234","5678","0910");
catch (Exception ex)
Console.WriteLine(ex.Message);
在我的 PrimaryDLL.cpp 文件中,我最终希望返回该对象,但因为它对如何引用指针以及何时离开:
return keyset;
un commented 我收到无法从对象类型转换为字符串类型的错误。
所以请大家帮忙解释一下为什么我会收到“尝试读取或写入受保护的内存。这通常表明其他内存已损坏”错误。
最好的问候。
【问题讨论】:
您不能 pinvoke 使用 C++ 类作为参数或返回值的函数。仅支持 C 字符串,char* 或 wchar_t*。返回 C 字符串也不行。 P/invoke 不是为支持类而设计的。一点儿都没有。您可能会考虑使用 COM 互操作或 C++/CLI “It Just Works”互操作。 【参考方案1】:std::string tKeyset::DiversifyKeyset(...) return 0;
这会调用 std::string(const char*) 构造函数,传递一个 NULL 指针。这是非法的。
【讨论】:
好的,那我应该怎么做呢?我想返回我创建的对象,但我也有这个问题。我收到:code
13 IntelliSense:不存在合适的构造函数来将“firstDllLayer::tKeyset *”转换为“std::basic_stringtKeyset*
,为什么要声明返回类型为std::string
的函数?
因为它会引发我需要指定返回类型的错误。它假定为 int 但我不确定我应该在这里声明什么。我想返回对象,但不确定如何......
如果你想让一个函数返回一个类型为T的值,那么你自然应该指定T作为该函数的返回类型。
好吧,我不明白,所以我现在改变了:std::string tKeyset::DiversifyKeyset(...) return 0; to tKeyset tKeyset::DiversifyKeyset(...) return keyset;我在 PrimaryDll.h 文件中也声明了这个,以便消除任何混淆: static PRIMARYDLL_API tKeyset DiversifyKeyset(std::string KIC, std::string KID, std::string KIK);而不是返回类型 std::string。返回键集时出现错误;它说:不存在合适的构造函数来将“firstDllLayer::tKeyset *”转换为“firstDllLayer::tKeyset”所以我意识到它是一个指针但是如何引用?【参考方案2】:
一般来说,在 c++ dll 中创建一个对象并将其交还给 c# 是一个坏主意。内存的所有权成为一个问题。字符串是可以的,因为它们通过从 c 内存到 c# 拥有的内存进行编组。
我建议你通过分别传递三个字符串并在 c# 中构建你的 keyset 对象来简化整个事情。
另一种选择是将键集对象展平为二进制字节数组。然后有一个自定义函数,当你把它重新展平成一个对象时。
这是我之前做过的事情
[DllImport("Helper.dll", CharSet = CharSet.Auto)]
private static extern bool GetStuff(
[MarshalAs(UnmanagedType.LPStr)] StringBuilder inputString,
byte[] outputBuffer,
UInt32 outputSize,
ref UInt32 outputFinalSize);
public static void DoStuff(string inputString)
int buffSize = 4000;
byte[] buff = new byte[buffSize];
StringBuilder inputString = new StringBuilder();
inputString.Append(blobs);
UInt32 size = 0;
if (!GetStuff(inputString, buff, (uint)buffSize, ref size) || size == 0)
//ERROR
when it comes back i just do
StringBuilder outputString;
for (uint i = 0; i < outputFinalSize; i++)
outputString.Append((char)buff[i]);
outputString.toString();
c signatrue 看起来像这样
extern "C" __declspec(dllexport) bool GetStuff(char* inputString, char* outputBuffer, UInt32 outputSize, UInt32& outputFinalSize);
请注意,我也使用字符串生成器来传递字符串,您可能不需要... 正如你所看到的,我在 c# 端分配了所有内容,因此我不必担心 c++ 的内存管理,当函数完成时,c++ 中的所有内容都应该被释放。
你的电话应该看起来像
private static extern bool DiversifyKeysetCallAndRetrunFlatBuffer(
[MarshalAs(UnmanagedType.LPStr)] StringBuilder inputStringA,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder inputStringB,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder inputStringC,
byte[] outputBuffer,
UInt32 outputSize,
ref UInt32 outputFinalSize);
std::string str1(inputStringA);
std::string str2(inputStringB);
std::string str3(inputStringC);
KeySet key = DiversifyKeyset(str1, str2, str3);
outputFinalSize = key.SerializeToBuffer(outputBuffer, outputSize);
if (outputFinalSize == 0)
return false;
return true;
int KeySet::SerializeToBuffer(char* buffer, size_t bufferSize)
//We are going to fill the buffer like so "key1|key2|key3"
size_t totalSize = Key1.size() + Key2.size() + Key3.size() + 4;
if (bufferSize < totalSize)
return 0; // buffer too small
char* bufferCurr = buffer;
memcpy(bufferCurr, Key1.c_str(), Key1.size());
bufferCurr += Key1.size();
bufferCurr[0] = '|';
bufferCurr++;
memcpy(bufferCurr, Key2.c_str(), Key2.size());
bufferCurr += Key2.size();
bufferCurr[0] = '|';
bufferCurr++;
memcpy(bufferCurr, Key3.c_str(), Key3.size());
bufferCurr += Key3.size();
bufferCurr[0] = '\0';
return totalSize;
最后,在 c# 端,您可以将缓冲区转换为字符串并通过 | 进行拆分。获取所有 3 个键,然后创建一个具有 3 个字符串的 c# KeySet 对象。
【讨论】:
好的,是的,这听起来是个好主意。我可以在 C# 中构建 tKeyset 对象,然后将字符串作为我在 C++ 中构建的参数传递吗?那我怎样才能返回字符串呢? 好吧,我得研究一下这个方法。所以你认为我从 C++ 返回一个对象会对内存分配产生不利影响?您是否看到我可以让 DLL 返回对象的任何方法?似乎更多的是我现在的目标,或者至少对我来说是可行的。 分配的问题是您不能只将指针传回,因为 dll 可能会删除内存,或者您必须将其保留在那里并通过不同的调用将其删除。因此,从 c 到 c# 使用编组,其中对象的全部内容被复制以进行传递。使内存管理保持独立。不幸的是,CLR 编组器不知道如何编组比基本类型的结构更复杂的东西。 因此您必须选择可以在托管 c++ 中围绕您的 c 调用添加另一个包装器,然后调用托管 c++ 函数并直接获取对象(因为它们都是托管 GC 将处理内存)。或者就像我说的将对象序列化为字节缓冲区,然后具有将对象从 c# 中的 char 缓冲区反序列化为 c# 对象的函数。我已经展示了如何传递 char 缓冲区。 您好!好的,所以我正在尝试实现上面的解决方案,以将我的键集对象展平为字节数组。我只是对如何在 c# 中调用它感到有点困惑,因为我导入了我的 DLL,然后同时拥有这两个函数,你和我的像这样:private static extern bool GetStuff([MarshalAs(UnmanagedType.LPStr)] StringBuilder inputString, byte[] outputBuffer, UInt32 outputSize, ref UInt32 outputFinalSize); public static extern string DiversifyKeysetCall(string a, string b, string c);
isDiversifyKeysetCall 是传递一个字符串还是一个对象?以上是关于C++ DLL 到 C# 错误:“试图读取或写入受保护的内存。这通常表明其他内存已损坏。”的主要内容,如果未能解决你的问题,请参考以下文章
“试图读取或写入受保护的内存。这通常表明其他内存已损坏” DllImporting C#
尝试读取或写入受保护的内存。这通常表明其他内存已损坏。在 C++ DLL 中