.Net调用非托管代码数据类型不一致的问题

什么是Net互操作?.Net不能直接操作非托管代码,这时就需要互操作了。
   c#中调用非托管c++函数,此函数又包含指向某个结构的指针,譬如指向c#中的byte数组。对于这样的参数,考虑到非托管变量不能直接在托管代码中使用,那么应该如何去处理呢?

 上例子:

 private string getSelText(int start,int Scount)
      {
          try
          {
              StringBuilder a = new StringBuilder(Scount);
             
              IntPtr pdf_pag = FPDFView.FPDF_LoadPage(pdf_doc, currentPage);
              IntPtr cur_textpag = FPDFText.FPDFText_LoadPage(pdf_pag);
              int count=Scount/512;
              for (int i = 0; i <= count; i++)
              {
                  byte[] copyText = new byte[1024];//托管数组
                  IntPtr pdata = Marshal.AllocHGlobal(1024);//申请内存
                  Marshal.Copy(copyText, 0, pdata, 1024);//再向里copy数据
                  if (i != Scount / 512)
                  {
                      FPDFText_GetText(cur_textpag, start + i * 512, 512, copyText);//非托管c++函数,参数copyText是带有返回数据的非托管数组
                  }
                  else
                  {
                      FPDFText_GetText(cur_textpag, start + i * 512, Scount % 512, copyText);
                  }
                  Marshal.FreeHGlobal(pdata);//以及释放内存
                a.Append(  UnicodeEncoding.Unicode.GetString(copyText));
                  
              }
             
              DeleteObject(pdf_pag);
              DeleteObject(cur_textpag);
              return a.ToString();
          }
          catch (Exception ex)
          {
              
              MessageBox.Show(ex.Message);
              return null;
          }
         
      }

(此处需注意的是:需要非托管指针byte[]参数的内存要先申请,再向里copy数据;还有最后要记得释放申请的内存. )

     细心的读者可能会发现方法中有总的数据长度,为什么要分n个固定长度去读取呢?这是因为要操作的数据过大时,程序就会报内存不够用的错误,所以使用小数组循环读取,避免此问题的出现。可为什么数组长度是1024,而每次读取的长度是512呢?假如我们使用长度为512的数组 结果会怎样呢?

     答案是只会返回一部分字符串数据,那缺少的部分哪去了呢,问题出在哪呢?

     很容易也很关键的就想到,byte[]长度不够,字符数据被截取了,问题很可能就出在c++非托管代码返回的数据类型与c#托管代码数据类型不一致造成的偏差(定义函数的人真坑)。我们转到定义查看FPDFText_GetText的声明。

c#代码:

        [DllImport("fpdfsdk.dll", CharSet = CharSet.Unicode)]
        public static extern int FPDFText_GetText(IntPtr text_page, int start_index, int count, byte[] result);

    没错啊,byte[] 类型的,让我们再来看下关于此非托管函数的c++文档说明:

DLLEXPORT int STDCALL FPDFText_GetText ( FPDF_TEXTPAGE  text_page,
    int  start_index,
    int  count,
    unsigned short *  result 
  )

   答案出现了, c++的unsigned short类型的指针能不能用c#中byte[]类型承接?查资料 ,非托管c++unsigned short与c#中的  System.UInt16是等价的,均是 16 位的,而c#的byte是8位的,难怪总是出现数据丢失的现象。

   该怎么解决呢?由于c#中没有现成的UInt16转字符串的方法,唯有抛弃UInt16,改用原来的byte,只要每次读入固定的长度,而承接的byte[]的长度是该长度的两倍即可。

另一种解决办法是使用Marshal.PtrToStringUni(ptr)转化,代码如下:

 int BufferLength = 0;            
              BufferLength = FPDFText.FPDFText_PageToText(pdf_doc, CurrentPage, IntPtr.Zero, 0, 0);
              IntPtr ptr = Marshal.AllocCoTaskMem(BufferLength * sizeof(UInt16));//申请内存大小,Uint16,16位等价于c/c++中wchar_t,unsigned short
              FPDFText.FPDFText_PageToText(pdf_doc, CurrentPage, ptr, BufferLength, 0);
              string str = Marshal.PtrToStringUni(ptr);      

 

 

 

posted on 2013-10-10 16:41  NLazyo  阅读(1256)  评论(0编辑  收藏  举报