《Win32类型和.net类型的对应表》及感悟例子

Win32 类型和 .net 类型的对应表

Figure 2 Non-Pointer Data Types

Win32 Types

Specification

CLR Type

char, INT8, SBYTE, CHAR†

8-bit signed integer

System.SByte

short, short int, INT16, SHORT

16-bit signed integer

System.Int16

int, long, long int, INT32, LONG32, BOOL†, INT

32-bit signed integer

System.Int32

__int64, INT64, LONGLONG

64-bit signed integer

System.Int64

unsigned char, UINT8, UCHAR†, BYTE

8-bit unsigned integer

System.Byte

unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR†, __wchar_t

16-bit unsigned integer

System.UInt16

unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT

32-bit unsigned integer

System.UInt32

unsigned __int64, UINT64, DWORDLONG, ULONGLONG

64-bit unsigned integer

System.UInt64

float, FLOAT

Single-precision floating point

System.Single

double, long double, DOUBLE

Double-precision floating point

System.Double

†In Win32 this type is an integer with a specially assigned meaning; in contrast, the CLR provides a specific type devoted to this meaning.

   对于简单类型的对应,已经差不多就可以解决掉了。但是如果要将如下两个函数 ( 海量智能分词研究版 中提供的接口函数中选择出来的,因为他提供了文档说明 J 适合做例子 ) 转成 C# 中的版本,该如何操作呢?这就需要涉及到数据封送处理了。数据封送处理是 P/Invoke 具有挑战性的方面。当在托管和非托管代码之间传递数据时,就需要用到 Marshal 这个很有用的东西了。

Function 1: SHLSegWord* HLGetFileKeyAt(HANDLE hHandle, int iIndex);

Function 2: bool HLGetFingerM (HANDLE hHandle, LPBYTE &rpData, DWORD &rdwLen);

struct SHLSegWord

{

     char *s_szWord;

     DWORD s_dwPOS;

     float   s_fWeight ;

    

     SHLSegWord()

     {

            Reset();

     };

     void Reset()

     {

            s_szWord = NULL ;

            s_dwPOS = 0 ;

            s_fWeight = 0;

     };

};

对于 Function 1 一开始,可能一看到他的返回值是一个 struct ,就会自然地想到使用 C# 写一个和 SHLSegWord 一摸一样的结构体,但是这样是不行的,因为 Function 1 返回的是一个 SHLSegWord* ,是一个指向结构体的指针,因此就不能使用 C# 中为值类型的 struct 来接收这个函数的返回值,是错误的。而 C# 中的 class 却是引用类型的,因此使用 class 是可行的,于是我就写了一个对应的:

namespace PInvoke.By.Jizhou

{

    [StructLayout (LayoutKind .Sequential, CharSet = CharSet .Ansi)]   // 这个 attribute 是必须的,不然编译器会改变字段的顺序和大小,就没有办法做到内存对齐了

    public class SHLSegWord

    {

         public string word = "";  // StringBuilder ??

        public int POS = 0;

        public float weight = 0;

    }

}

接下来,用 Marshal.SizeOf 得到其大小,进行最初步的测试,看看它和在 C/C++ 程序中用 sizeof 得到的大小是否相等,测试结果显示,相等,于是我就继续开始往下做,把 Function 1 写成:

[DllImport ("HLSSplit.dll" )]

public static extern SHLSegWord HLGetWordAt(IntPtr hHandle, int nIndex);

    编译,通过, OK !太爽了,没想到这么顺利,于是就开始测试。可是到函数一执行,就让我大跌眼镜。一下子崩出来如下 exception :“尝试读取或写入受保护的内存。这通常指示其他内存已损坏”,更让人郁闷的是,仔细查看后,发现循环调用这个函数时,居然是在 index 5 时出错,于是加上 try catch ,嘿,居然 index 6 7 8 9 就可以通过,但是 index=10 又不行,之后一直到 24 却又可以。观察了一下,出错的 index 之间没有任何联系,也找不出任何规律,郁闷 ing 。于是我将 SHLSegWord 结构体中的 public string word = ""; 修改承了 public StringBuilder word = new StringBuilder () ,可是问题依然存在。实在有点 太诡异了,于是请教我的好朋友夏桅 (CSDN 上的 suma) ,他是 P/Invoke 方面的牛人。他建议让我先把 public string word = ""; 改成 public IntPtr word = new IntPtr (); 原因在于非内嵌的 string 不能放在传出参数里面,毕竟传出参数和传入参数是不同的,传出的话需要预先分配缓冲区, string 是固定了值的类型,所以一般用 stringbuilder 。但是这个又是在结构体里面的 string ,稍微复杂一点,所以使用 IntPtr 应该是最好的方法了,因为 IntPtr 类型可以由支持指针的语言使用,并可作为在支持与不支持指针的语言间引用数据的一种通用方式。修改过后,错误仍然存在。仔细看看,发现原函数也返回的是一个指针,于是就觉得是不是应该也将 public static extern SHLSegWord HLGetWordAt(IntPtr hHandle, int nIndex); 的返回类型,也修改成 IntPtr ,然后再使用 Marshal .PtrToStructure() 来接收数据,应该就比较安全了。果然,修改后,问题立马消失,至此,搞定。下面是较完整的示例代码:

1 SHLSegWord 定义

namespace PInvoke.By.Jizhou

{

[StructLayout (LayoutKind .Sequential, CharSet = CharSet .Ansi)]

public class SHLSegWord

    {

         public IntPtr word = new IntPtr ();

        public int POS = 0;

        public float weight = 0;

    }

}

2 HLGetWordAt 函数定义

[DllImport ("HLSSplit.dll" )]

public static extern IntPtr HLGetWordAt(IntPtr hHandle, int nIndex);

3 .调用代码:

IntPtr wordPtr = HLSegFunc .HLGetWordAt( hHandle , i);

SHLSegWord wordStruct = (SHLSegWord )Marshal .PtrToStructure(wordPtr, typeof (SHLSegWord ));

string word = Marshal .PtrToStringAnsi(wordStruct.word);

至此, Function 1 的示例叙述完毕,下面看看 Function 2:

bool HLGetFingerM (HANDLE hHandle, LPBYTE &rpData, DWORD &rdwLen);

这个家伙同样长的比较怪异,第一个参数比较好办,使用 IntPtr 即可,第三个参数,转化为 int ,然后使用 C# out 修饰符即可。可是第二个参数是指向指针的指针,有点难办了,再看看海量给出来的 demo C++ 代码, rpData 居然是一个数组,这下给接收数据带来了不小的麻烦,我尝试将他转成

bool HLGetFingerM ( IntPtr hHandle, out byte[] rpData , out int rdwLen );

但是结果不正确。几经 try ,都得不到正确的结果,无奈中又想起了 IntPtr 这个救苦救难的兄弟,换成 IntPtr try ,唉,还真好了。下面给出实现代码:

1.  函数定义:

bool HLGetFingerM ( IntPtr hHandle, out IntPtr rpData , out int rdwLen );

2.  调用代码示例

IntPtr data = new IntPtr ();

int dataLen = 0;

bool successOrNot = HLSegFunc .HLGetFingerM(hHandle, out data, out dataLen);

if (successOrNot)// 获得语义指纹

{

   for (int j = 0; j < dataLen; j++)

   {

       IntPtr dataSnippet = Marshal .ReadIntPtr(data, j);

       string strU = Marshal .ReadByte(data, j).ToString("x2" );

   }

}

 

关于这个主题,可以参考以下资料: Marshaling Data with Platform Invoke Platform Invoke Data Types An Introduction to P/Invoke and Marshaling on the Microsoft .NET Compact Framework

至此,全部完毕,总一个小的总结:

1.  在使用 DllImport Win32 函数导入成 C# 对应的函数时,一定要注意 Win32 函数中的参数是传入还是传出,是值类型还是引用类型;

2.  对于 C/C++ 中的复杂类型如 Struct ,转成 C# 对应的数据结构时,最好使用 C# Class 而不是值类型的 Struct

3.  对于 C/C++ 中的指针类型,最好使用 IntPtr 类型来进行数据的封送处理;

4.  .NET 的字符串在内存中总是以 Unicode 方式编码的,但对于 API 函数需要 string 的编码,有时取决于 Windows 版本,有时需要显式设定,有时又需要显式转换。

5   使用与基础 API 函数类型不同但与之兼容的 CLR 类型是 P/Invoke 较难使用的一个方面,这就需要多多实践,积累经验。

6. 上面费劲搞了这么多,就源于我没有 DLL 的源代码,如果你有 C/C++ DLL 的源代码,那么问题就简单多了。这里有解决方法: How do I mix C# and C++ code in a single assembly? 引用其方法如下:

How do I mix C# and C++ code in a single assembly?

If your C++ code is not compiled with /clr:safe (i.e. it is compiled with /clr or /clr:pure), do the following:

1) compile your C++ code into .obj files

2) compile your C# code into a .netmodule, using /AddModule to reference the C++ .obj files

3) link the C# netmodule directly with the C++ object files using the C++ linker to create a mixed language assembly

  If your C++ code is compiled with /clr:safe, build your C++ code as a .netmodule.  You can use it just like you would use a .netmodule from any other language.

This applies to .Net framework beta2+.

6.  如果你对 P/Invoke 感兴趣的话,这本书上面有提到一些,可以参考一下。 .Net Compact Framework Programming with C#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值