2012-03-14 85 views
4

我正在工作的标签阅读器,我能够连接它并阅读一些数据。我的问题是当我试图读取标签ID,这是一个大字符序列。尝试读取或写入受保护的内存时尝试通过C#中的大字符序列

该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); 

Dataflag,计数,站号主要其中一个uint类型做得很好小序列。但是当涉及到这是一个很大的序列。我试过型但它抛出这个异常:

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

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

    didnt解决问题

  • 计数正确返回

  • 我的操作系统是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** 
      // 
+1

使用StringBuilder替代,降* REF *。一定要用足够大的容量来初始化它。猜测它错误腐化堆。 – 2012-03-14 19:48:41

+0

tank hans, StringBuilder value = new StringBuilder(“test”,1000); 返回“{}”没有值。 有什么想法? – john 2012-03-14 20:15:51

+0

看起来不错,pinvoke调用至少会将构建器重置为空字符串。当然不知道为什么。 – 2012-03-14 20:21:12

回答

4

你不应该需要使用corflags application.exe位/ 32位+。你需要做的就是在project/properties/build中将平台目标设置为x86。

这将工作(以及它使用我创建的测试本机方法与上面给出的相同签名)。 第一种方法不要求不安全的关键字,或要求使用“允许不安全的代码”设置为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; 
} 

下面是一个使用固定阵列字节的替代方案。 第二种方法需要不安全的关键字,并且该项目是使用'允许不安全代码'设置为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参数中的方法中,并且退出时的这个值将是所用缓冲区的数量。如果字节数组需要转换为字符串,我已经允许一个额外的字节来确保有一个空终止字符。

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

这是我的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 = "[email protected]")] 
public static extern unsafe short GetIdBuffer(... 

可以安全地指定传递到方法(不包括值数组参数)比其他字节作为基本类型,诸如整型,长等,这是因为:1)值在参考传递的参数和2)x86使用little-endian字节排序。这导致单字节被写入传入的四字节int的最低有效字节。

虽然在这种情况下使用匹配类型,字节是可取的。

+0

除非非托管代码在调用返回后写入缓冲区,否则无需修复它。在正常情况下,您可以传递一个字节数组,让CLR担心修复缓冲区/编组数据。 – Yaur 2012-03-17 19:14:48

+0

我试过没有修复。我无法让它编译。我要么得到'指针只能用在不安全的上下文中'或'你只能在固定语句初始化器中获取未固定表达式的地址'。 – Phil 2012-03-17 20:56:59

+0

没错。你只是将它作为一个字节数组传递......修正不是必需的。 'public static extern unsafe short GetIDBuffer(IntPtr hCom,ref byte dataFlag,ref byte count,byte [] value,ref byte stationNum);' – Yaur 2012-03-17 23:35:37

0

在您的P/Invoke中尝试使用SafeHandle而不是IntPtr。有可能hCom的托管包装正在GC'd并在您的本地调用中间完成,导致hCom无效。

+0

呵呵?它看起来像他正在使用一个完全非托管的句柄,唯一的托管引用是一个IntPtr,它只是一个int大小的void * ......我不确定垃圾收集是如何发挥作用的。 – Yaur 2012-03-16 21:22:33

2

您对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是理智的,你将初始化count变量来让被调用者知道缓冲区的大小。

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

如果这两个都不是真的,我们必须假定返回值是写入的字节数,因为这是短小的,所以你需要传入一个长度至少为65535字节的缓冲区。

如果没有这些工作,您将需要致电供应商,并找出如何指定缓冲区的大小,因为这本身并不真正是互操作问题。

您根本不需要使用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+"\""); 
+0

你是对的还是错的。字节是最好的,但UInt将在这种情况下工作。请参阅下面的答案。 – Phil 2012-03-17 15:37:30

+0

@phil是的,对于小于255的值是正确的......然而,从他的评论看来,他正在传递一个1000字节的缓冲区,所以他会沉默地误解数据而不是编译时错误。无论如何,这个问题很可能是他被视为出局而不是输入/输出变量。 – Yaur 2012-03-17 19:11:06

+0

谢谢你解决的问题:string hex = BitConverter.ToString(bytes).Replace(“ - ”,string.Empty);将字节数组转换为字符串返回了标记ID – john 2012-03-18 23:55:26

相关问题