试图在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**
//
您不需要使用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
无效。