【问题标题】:Attempted to read or write protected memory. This is often an indication that other memory is corrupt. Acess c++ dll尝试读取或写入受保护的内存。这通常表明其他内存已损坏。访问 c++ dll
【发布时间】:2014-07-24 09:22:06
【问题描述】:

我将 .cpp 文件转换为 .dll 文件并在我的 vb.net 项目中使用这个 .dll 文件。在我的 vb.net 项目中访问 c​​++ dll,我收到此错误,“尝试读取或写入受保护的内存。这通常表明其他内存已损坏。”可以在这里得到任何帮助吗? 这是我的 vb.net 代码:

Public NotInheritable Class myTestDLL
    <DllImport("encryption.dll", EntryPoint:="conv16to64", CallingConvention:=CallingConvention.Cdecl)>
    Public Shared Sub conv16to64(ByVal c As String, ByRef s As String)
    End Sub
End Class

这是我的 c++ 函数代码:

 extern "C"{
_declspec(dllexport) void conv16to64(char s[], char b[])
{
    int i,j;
    char t[65],c[65];

    for (i=0; i<=16; i++)
        c[i] = s[i];

    for (i=1; i<=64; i++)
        t[i] = 0;

    for (i=0; i<=15; i++)
    {
        if (i == 0)
            j = 0;
        else if (i == 1)
            j = 4;
        else if (i == 2)
            j = 8;
        else if (i == 3)
            j = 12;
        else if (i == 4)
            j = 16;
        else if (i == 5)
            j = 20;
        else if (i == 6)
            j = 24;
        else if (i == 7)
            j = 28;
        else if (i == 8)
            j = 32;
        else if (i == 9)
            j = 36;
        else if (i == 10)
            j = 40;
        else if (i == 11)
            j = 44;
        else if (i == 12)
            j = 48;
        else if (i == 13)
            j = 52;
        else if (i == 14)
            j = 56;
        else if (i == 15)
            j = 60;

        switch (c[i]) {
        case '0': t[j+1] = 0; t[j+2] = 0;
            t[j+3] = 0; t[j+4] = 0;
            break;
        case '1': t[j+1] = 0; t[j+2] = 0;
            t[j+3] = 0; t[j+4] = 1;
            break;
        case '2': t[j+1] = 0; t[j+2] = 0;
            t[j+3] = 1; t[j+4] = 0;
            break;
        case '3': t[j+1] = 0; t[j+2] = 0;
            t[j+3] = 1; t[j+4] = 1;
            break;
        case '4': t[j+1] = 0; t[j+2] = 1;
            t[j+3] = 0; t[j+4] = 0;
            break;
        case '5': t[j+1] = 0; t[j+2] = 1;
            t[j+3] = 0; t[j+4] = 1;
            break;
        case '6': t[j+1] = 0; t[j+2] = 1;
            t[j+3] = 1; t[j+4] = 0;
            break;
        case '7': t[j+1] = 0; t[j+2] = 1;
            t[j+3] = 1; t[j+4] = 1;
            break;
        case '8': t[j+1] = 1; t[j+2] = 0;
            t[j+3] = 0; t[j+4] = 0;
            break;
        case '9': t[j+1] = 1; t[j+2] = 0;
            t[j+3] = 0; t[j+4] = 1;
            break;
        case 'A': t[j+1] = 1; t[j+2] = 0;
            t[j+3] = 1; t[j+4] = 0;
            break;
        case 'B': t[j+1] = 1; t[j+2] = 0;
            t[j+3] = 1; t[j+4] = 1;
            break;
        case 'C': t[j+1] = 1; t[j+2] = 1;
            t[j+3] = 0; t[j+4] = 0;
            break;
        case 'D': t[j+1] = 1; t[j+2] = 1;
            t[j+3] = 0; t[j+4] = 1;
            break;
        case 'E': t[j+1] = 1; t[j+2] = 1;
            t[j+3] = 1; t[j+4] = 0;
            break;
        case 'F': t[j+1] = 1; t[j+2] = 1;
            t[j+3] = 1; t[j+4] = 1;
            break;
        }
    }
    for (i=1; i<=64; i++)
        b[i] = t[i];
}

【问题讨论】:

  • 所以这不可能是您没有向我们展示的 C++ 代码中的错误,对吧?
  • 另外,这些只是定义,并没有告诉我们任何信息。如何发布您如何调用这些函数,这些字符数组是否有效,DLL 代码是否正确处理这些数组等。
  • 不是一个大的 if/else 链,你不能只使用j = i * 4;吗?

标签: c++ vb.net dll import


【解决方案1】:

检查你的代码有几个问题,最大的两个是:

您正试图在未指定类型的情况下跨本机域检索字符串。

如上所述,您正在尝试读取 16 个字符,但将它们引用为 17(您是否考虑使用空字符?)。

理想情况下,在这种情况下,您希望使用 C 执行以下两项操作: 创建您的字符串缓冲区比您的源预期大 1 个元素。 将缓冲区初始化为 null。

通过这样做,您可以创建一个更易于管理的数据处理程序,并且不仅不易出错,而且能够更轻松地从用户错误中恢复(并且具有缓冲性)。

首先要解决我所说的第一个问题,您有以下几点:

Public NotInheritable Class myTestDLL
    <DllImport("encryption.dll", EntryPoint:="conv16to64", CallingConvention:=CallingConvention.Cdecl)>
    Public Shared Sub conv16to64(ByVal c As String, ByRef s As String)
    End Sub
End Class

实际上应该是:

Public NotInheritable Class myTestDLL
    <DllImport("encryption.dll", EntryPoint:="conv16to64", _
        CharSet:=CharSet.Ansi, _
        CallingConvention:=CallingConvention.Cdecl)>
    Public Shared Sub conv16to64(ByVal c As String, _
        ByVal s As IntPtr)
    End Sub
End Class

在正常的字符串处理情况下,您的方法非常好。但是,如果您将字符用作数组,它们实际上不是字符串,不应被视为跨域的字符串。它变得太乱了。将其作为指针处理,并在本地进行翻译。这使您可以在 .net 方面管理错误情况,而不是让 .net 为您处理。如果您可以控制本机功能(我在下面有更多信息),您可以使其更加稳定,这样您就无需担心在 .net 端对其进行管理。

这样管理传入和传出的数据要容易得多。您分配一个 65 字节的数组,并在您的函数返回后,将字节数组编组为字符串。

正如所指出的,您正在对变量进行盲传。此类操作的新 Microsoft 标准实际上是:

extern "C"{
_declspec(dllexport) void conv16to64(const char* s, size_t inSize, char* b, size_t& outSize)
{ ...

或者:

extern "C"{
_declspec(dllexport) void conv16to64(const char* s, size_t inSize, char* b, size_t* pOutSize)
{ ...

这里的意义在于您希望能够验证您的尺寸。 VB 对这些的调用如下所示:

    <DllImport("encryption.dll", EntryPoint:="conv16to64", CharSet:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)>
    Public Shared Sub conv16to64(ByVal c As IntPtr, ByVal inSize as int64, ByVal s As IntPtr, ByRef outSize as int64)
    End Sub

我个人在 .net 中为我的导入创建了一个包装函数,它适用于更好的开发实践,并简化了在处理本地导入时往往会添加的许多逻辑。在这里你会注意到我只包装了我公开的函数。这实际上是因为它需要自定义逻辑。但是,如果我要公开 ZeroMemory 函数,我可能会创建一个包装器调用。这样我就可以处理调用中的异常(例如,如果库不可用时可以使用替代逻辑,或者允许更改库而不导致我公开的代码发生变化)。

使用您的示例,我的 VB 类实际上会有以下内容

Public NotInheritable Class myTestDLL
    <DllImport("kernel32.dll", EntryPoint:="RtlZeroMemory")>
    Private Shared Sub ZeroMemory(ByVal dst As IntPtr, length As Integer)
    End Sub

    <DllImport("encryption.dll", EntryPoint:="conv16to64", _
        CharSet:=CharSet.Ansi, _
        CallingConvention:=CallingConvention.Cdecl)>
    Private Shared Sub conv16to64(ByVal c As String, _
        ByVal s As IntPtr)
    End Sub
    Public Shared Function Convert16to64( _
        ByVal charTest As String) As String

        Dim incoming As IntPtr = Marshal.AllocHGlobal(65)
        Dim retVal As String = ""
        Try
            ZeroMemory(incoming, 65)
        Catch ex As Exception
            ' Add some exception handling here '
        End Try

        Try
            conv16to64(charTest, incoming)
            retVal = Marshal.PtrToStringAuto(incoming)
            Marshal.FreeHGlobal(incoming)
        Catch ex As Exception
            ' Add some exception handling here '
        End Try
        Return retVal
    End Function
End Class

这将是对您当前问题的直接 VB 修复。如前所述,您还需要在 C 代码中解决一些其他潜在问题。最大的一个是数据大小验证。以及整体数据完整性。请记住,如果没有空字符,识别字符串何时以及如何结束是一个谜,而这个谜会引起一些非常奇怪的反应,但大多只是崩溃。

在分配它们之前将您的字符归零(在某些方法中,最好在完成数据处理后将一个长度为 1 的空字符放在一个好主意,以确保,如果您不使用,这很方便固定长度操作)。

始终将 char 缓冲区初始化为比您想要的大一个字节,并在零填充时将最后一个 char 填充为 null。

最后,如果您知道字符类型,请指定它。自动很好,但具体是更好的做法。

这些事件的组合应该可以消除所有错误。

【讨论】:

    【解决方案2】:

    我不知道以下任何建议是否能解决您的问题,但您的代码存在问题(或奇怪)。

    void conv16to64(char s[], char b[])
    

    首先,当涉及到字符串时,DLL 和您的 VB 应用程序之间的约定是什么? sb 必须有多大才能确保没有内存覆盖?通常,DLL 函数带有另一个参数,表示要复制的最大字符数。

    其次,如果其中一个参数永远不会改变它所指向的内容,那么它应该是const。按照您的代码,该功能最好以这种方式完成:

    void conv16to64(const char* s, char* b);
    

    第三,数组从0开始索引到n-1,其中n是数组中的条目总数。你的循环看起来很可疑,因为它们要么从 1 开始,就像这里:

    for (i=1; i<=64; i++)
        b[i] = t[i];
    

    或者它们以使用&lt;= 作为终止条件的值结束。每当我看到这一点时,它往往是一个错误。像这样:

    for (i=0; i<=16; i++)
    

    您要复制 17 个字符,而不是 16 个字符。这可能会导致“off-by-1”内存覆盖错误。

    第四,在 cmets 中,您已经被告知有无穷无尽的 else 链。一个好的程序员总是在他们的代码中寻找重复的模式,并尝试修复它们。例如,您需要做的就是将 j 乘以 4。

    在您的 case 声明中,您可以简单地按照这些思路做一些事情(这甚至一点都不复杂):

    const char *binStrings[] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110",
                                 "0111", "1000", "1001", "1010", "1011", "1100", "1101",
                                 "1110", "1111"};
    //...
    const char *pBinString = 0;
    if ( c[i] >= '0' && c[i] <= '9' )
        pBinString = binStrings[c[i] - '0'];
    else
    if ( c[i] >= 'A' && c[i] <= 'F' )
        pBinString = binStrings[c[i] -'A' + 10];       
    if ( pBinString )
        memcpy(t+j, pBinString, 4);
    

    换句话说,只需要两个if() 语句来测试c[i] 的值,并且给定该值,它将用作具有必要字符串的数组的索引。上面的代码假定您在使用 ASCII 整理序列的计算机上运行。同样,这不是 最复杂的方法,但它至少消除了冗余。

    但是,您的问题很可能是您没有为该函数提供足够大的字符串,或者该函数由于可能发生的“off-by-1”错误而错误地覆盖了内存。

    【讨论】:

      猜你喜欢
      • 2014-03-22
      • 2013-01-23
      相关资源
      最近更新 更多