试图在C#中传递大的字符序列时,尝试读取或写入受保护的内存

本文关键字:读取 内存 受保护 字符 | 更新日期: 2023-09-27 18:27:44

我正在开发一个标签读取器,我能够连接它并读取一些数据。我的问题是当我试图读取标签id时,它是一个大的字符序列。

SDK是用C语言编写的,我正在开发一个C#应用程序。

short GetIDBuffer(HANDLE hCom, unsigned char* DataFlag, unsigned char * Count, 
      unsigned char *value, unsigned char* StationNum)

在我的C#应用程序中:

[DllImport("Reader2.dll",CharSet = CharSet.Ansi)]
public static extern short GetIDBuffer(IntPtr hCom, ref uint DataFlag, 
       ref uint Count, ref String value, ref uint StationNum);

数据标志、计数、站号主要是uint类型运行良好的小序列。但当涉及到时,它是一个大序列。我尝试了字符串类型,但它抛出了这个异常:

尝试读取或写入受保护的内存。这通常是指示其他内存已损坏。

  • [MarshalAs(UnmanagedType.LPWStr)]字符串值

    没有解决问题

  • 计数值正确返回

  • 我的操作系统是64位的:我使用了corflags application.exe/32bit+,并且我能够正确地加载dll。

代码快照:

 [DllImport("Reader2.dll")] 
    public static extern byte OpenReader(ref IntPtr hCom, byte LinkType, string com_port, uint port);
    [DllImport("Reader2.dll")]
    public static extern  short GetIDBuffer(IntPtr hCom, ref byte DataFlag, ref byte Count,**(type)** value , ref byte StationNum);
    static  void Main(string[] args)
    {
        byte count = 0, station = 1, flag = 0;
        IntPtr hcom = IntPtr.Zero;        
        OpenReader(ref hcom, 2, "192.168.0.178", 4001);
        // valid handle returned from openReader 
      //
        **GetIDBuffer code**
            //

试图在C#中传递大的字符序列时,尝试读取或写入受保护的内存

您不需要使用corflags application.exe/32bit+。您只需要在project/properties/build中将平台目标设置为x86。

这将起作用(使用我用上面给出的相同签名创建的测试本机方法也可以)。第一种方法不需要unsafe关键字,也不需要将"允许不安全代码"设置为true来构建项目。

internal static class NativeMethods
{
    [DllImport("Reader2.dll")]
    public static extern short GetIDBuffer(
           IntPtr hCom, ref byte dataFlag, ref byte count, 
           byte [] value, ref byte stationNum);
}
static int TestGetIDBuffer()
{
    const int arraySize = 255;
    byte[] bytes = new byte[arraySize + 1]; 
    byte dataFlag = 0;
    byte count = arraySize;
    byte status = 0;
    int retval = NativeMethods.GetIdBuffer(IntPtr.Zero, ref dataFlag, ref count, bytes, ref status);
    Debug.WriteLine(Encoding.ASCII.GetString(bytes));
    Debug.WriteLine(dataFlag);
    Debug.WriteLine(status);
    Debug.WriteLine(count);
    Debug.WriteLine(retval);
    return retval;
}

这里有一个使用固定字节数组的替代方案。第二种方法需要unsafe关键字,并且项目是在"允许不安全代码"设置为true的情况下生成的。

internal static class NativeMethods
{
    [DllImport("Reader2.dll")]
    public static extern unsafe short GetIDBuffer(
           IntPtr hCom, ref byte dataFlag, ref byte count, 
           byte* value, ref byte stationNum);
}
static unsafe int TestGetIDBuffer()
{
    const int arraySize = 255;
    byte[] bytes = new byte[arraySize + 1];
    byte dataFlag = 0;
    byte count = arraySize;
    byte status = 0;
    int retval;
    fixed (byte* buffer = bytes)
    retval = NativeMethods.GetIdBuffer(
             IntPtr.Zero, ref dataFlag, ref count, buffer, ref status);
    Debug.WriteLine(Encoding.ASCII.GetString(bytes));
    Debug.WriteLine(dataFlag);
    Debug.WriteLine(status);
    Debug.WriteLine(count);
    Debug.WriteLine(retval);
    return retval;
}

dataFlag、count和stationNum似乎都是输入/输出字节值。

正在填充的数据缓冲区是一个字节数组。这个缓冲区需要修复,这样GC在调用本机方法时就不会移动它。这在第一个示例中是隐式完成的,在第二个示例中则是显式完成的。

我假设可用的缓冲区大小应该在count参数中传递到方法中,并且在退出时这个值将是所使用的缓冲区量。如果字节数组需要转换为字符串,我允许额外的字节来确保有一个null终止字符。

实际上,固定语句有两种形式。MSDN文章中提到的一个允许您创建固定大小的数组,如公共固定字节字节数[ArraySize];MSDN文章中的另一个允许您固定变量的位置,以获取其地址。

这是我的C++测试代码:

extern "C" __declspec(dllexport) unsigned short __stdcall GetIDBuffer( 
    HANDLE hCom, unsigned char * dataFlag, unsigned char * count, 
    unsigned char* buffer,  unsigned char * status )
{
    memset(buffer, 0x1E, *count);
    *dataFlag = 0xa1;
    *count = 0x13;
    *status = 0xfe;
    return 0x7531;
}

上面给出的C#代码和我的测试代码之间的唯一区别是,由于我使用了C++编译器,所以必须以不同的方式指定入口点,例如

[DllImport("PInvokeTestLib.dll", EntryPoint = "_GetIDBuffer@20")]
public static extern unsafe short GetIdBuffer(...

您可以安全地将传递给方法的参数(不包括值数组参数)指定为字节以外的基元类型,如int、long等。这是因为1)值是在引用中传递的,2)x86使用小端字节排序。这导致单个字节被写入传入的四字节int中的最低有效字节

不过,建议使用匹配的类型,在这种情况下为字节。

您对Count的定义是错误的,它应该是基于本地原型的ref byte而不是ref Uint。如果您将其更改为正确的类型,并使用传递给StringBuilder构造函数的值对其进行初始化,则一切都应该正常。。。如果没有,我会退一步,使用字节数组而不是StringBuilder来帮助更好地了解非托管代码的作用。

编辑:

您得到的错误表明存在缓冲区溢出

[DllImport("Reader2.dll")]
public static extern  short GetIDBuffer(IntPtr hCom, ref byte DataFlag, ref byte Count,**(type)** value , ref byte StationNum);
static  void Main(string[] args)
{
    byte count = 0, station = 1, flag = 0; //this right here is probably your problem
    IntPtr hcom = IntPtr.Zero;

您正在调用的非托管代码无法知道您传入的缓冲区有多大。如果API正常,您将初始化计数变量,让被调用方知道缓冲区的大小。

如果不是这样,您将需要查看文档,以了解您需要提供多大的缓冲区。

如果这两种情况都不成立,我们必须假设返回值是写入的字节数,并且由于这是一个短值,因此您需要传入一个至少65535字节长的缓冲区。

如果这些都不起作用,您将需要致电供应商,了解如何指定缓冲区的大小,因为这本身并不是一个互操作问题。

您也根本不需要使用fixed。fixed的目的是允许您提供一个将在多个非托管调用之间使用的指针,或者编写稍微快一点的托管代码(由于无边界检查),因为这两个应用程序都不使用字节数组。

你完成的代码应该看起来像:

[DllImport("Reader2.dll")]
public static extern short GetIDBuffer(IntPtr hCom, ref byte dataFlag, ref byte count,byte [] value, ref byte stationNum);
// ...
byte[] value = new byte[65536];
byte count = 255; //does 255 imply some buffer size?
short len = GetIDBuffer(hCom, ref dataFlag, ref count,value, ref stationNum);
var s1 = Encoding.ASCII.GetString(value,0,count);
var s2 = Encoding.ASCII.GetString(value,0,len);
Console.WriteLine("using count gives'""+s1+"'"");
Console.WriteLine("using return value gives'""+s2+"'"");

尝试在p/Invoke中使用SafeHandle而不是IntPtr。hCom周围的托管包装器可能会在本机调用过程中进行GC处理并最终确定,从而导致hCom无效。