代码改变世界

.net 互操作之p/invoke- 数据封送(嵌套结构体,类,数组)(5)

2010-08-26 23:50  Clingingboy  阅读(2300)  评论(0编辑  收藏  举报

嵌套结构体

  复杂的数据类型往往结构体中还有结构体,即嵌套结构体,也可以说是复杂类型.如下定义

//typedef struct _PERSONNAME
 //{
 //    char* first;
 //    char* last;
 //    char* displayName;
 //} PERSONNAME, *PPERSONNAME;
 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
 public struct PersonName
 {
     public string first;
     public string last;
     public string displayName;
 }
 
 //typedef struct _PERSON
 //{
 //    PPERSONNAME pName;
 //    int age; 
 //} PERSON, *PPERSON;
 [StructLayout(LayoutKind.Sequential)]
 public struct Person
 {
     public IntPtr name;
     public int age;
 }

Person中的name属性被定义成IntPtr,

测试代码

1.使用IntPtr 定义结构体

Console.WriteLine("\n结构体作为引用类型成员");
 // 创?建?名?字?
 PersonName name = new PersonName();
 name.last = "Cui";
 name.first = "Xiaoyuan";
 
 // 创?建?人?
 Person person = new Person();
 person.age = 27;
 
 IntPtr nameBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(name));
 Marshal.StructureToPtr(name, nameBuffer, false);
 
 person.name = nameBuffer;
 
 Console.WriteLine("调?用?前?显?示?姓?名?为?:?{0}", name.displayName);
 TestStructInStructByRef(ref person);
 
 PersonName newValue =
     (PersonName)Marshal.PtrToStructure(person.name, typeof(PersonName));
 
 // 释?放?在?非?托?管?代?码?中?分?配?的?PersonName实?例?内?存?
 Marshal.DestroyStructure(nameBuffer, typeof(PersonName));
 
 Console.WriteLine("调?用?后?显?示?姓?名?为?:?{0}", newValue.displayName);

分析如下代码片段
//Marshal.SizeOf(name)计算此对象非托管内存所需大小,用AllocCoTaskMem申请内存
IntPtr
nameBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(name));
//将对象封送到非托管内存中 Marshal.StructureToPtr(name, nameBuffer, false);

//将非托管对象封送到托管对象中
PersonName
newValue = (PersonName)Marshal.PtrToStructure(person.name, typeof(PersonName)); // 释放在?非托管代码中分配的PersonName实例内存 Marshal.DestroyStructure(nameBuffer, typeof(PersonName));

2.使用结构体实例

这样内存即自动管理

Console.WriteLine("\n结?构?体?作?为?值?类?型?成?员?");
 Person2 person = new Person2();
 person.name.last = "Huang";
 person.name.first = "Jizhou";
 person.name.displayName = string.Empty;
 person.age = 26;
 
 TestStructInStructByVal(ref person);

3.对象单一化

若非托管对象字段是嵌套结构体,托管代码可以声明为单一对象

[StructLayout(LayoutKind.Sequential)]
 public struct Person2_Flattened
 {
     public string first;
     public string last;
     public string displayName;
     public int age;
 }

3.1测试

Console.WriteLine("\n结?构?体?作?为?值?类?型?成?员?(?flattened)?");
 Person2_Flattened person = new Person2_Flattened();
 person.last = "Huang";
 person.first = "Jizhou";
 person.displayName = string.Empty;
 person.age = 26;
 
 TestStructInStructByVal(ref person);

类引用传递


1.NonBlittlable引用类型

因为其为引用类型,所以无须使用ref修饰符
也是以StructLayout标记

//typedef struct _MSEMPLOYEE
  //{
  //    UINT  employeeID;
  //    short employedYear;
  //    char* displayName; 
  //    char* alias; 
  //} MSEMPLOYEE, *PMSEMPLOYEE;
  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
  private class MsEmployee
  {
      private uint _employeeID;
      private short _employedYear;
      private string _displayName;
      private string _alias;
 
      #region Properties
      public uint EmployeeID
      {
          get { return _employeeID; }
          set { _employeeID = value; }
      }
 
      public short EmployedYear
      {
          get { return _employedYear; }
          set { _employedYear = value; }
      }
 
      public string DisplayName
      {
          get { return _displayName; }
          set { _displayName = value; }
      }
 
      public string Alias
      {
          get { return _alias; }
          set { _alias = value; }
      }
      #endregion
  }

2.非托管代码

注意引用类型需要申请内存

void __cdecl GetEmployeeInfo(PMSEMPLOYEE pEmployee)
 {
     if(NULL != pEmployee)
     {
         // 我?们?应?该?根?据?pEmployee->employeeID查?找?写?信?息?回?来?
         pEmployee->employedYear = 2;
         pEmployee->alias = (char*)CoTaskMemAlloc(255);
         pEmployee->displayName = (char*)CoTaskMemAlloc(255);
 
         strcpy_s(pEmployee->alias, 255, "xcui");
         strcpy_s(pEmployee->displayName, 255, "Xiaoyuan Cui");
     }
 }

因为是引用类型,所以注意这里指定了In,Out

[DllImport(_dllName, CallingConvention = CallingConvention.Cdecl,
     CharSet = CharSet.Ansi)]
 private extern static void GetEmployeeInfo([In, Out] MsEmployee employee);

测试代码

MsEmployee employee = new MsEmployee();
 employee.EmployeeID = 10001;
 
 GetEmployeeInfo(employee);
 
 Console.WriteLine("\n员?工?信?息?:");
 Console.WriteLine("ID: {0}", employee.EmployeeID);
 Console.WriteLine("工?龄?:{0}", employee.EmployedYear);
 Console.WriteLine("Alias: {0}", employee.Alias);
 Console.WriteLine("姓?名?: {0}", employee.DisplayName);

3.Blittable引用类型

类内部全为值类型

//typedef struct _SIMPLESTRUCT
 //{
 //    int    intValue;
 //    short  shortValue;
 //    float  floatValue;
 //    double doubleValue;
 //} SIMPLESTRUCT, *PSIMPLESTRUCT;
 [StructLayout(LayoutKind.Sequential)]
 private class ManagedClassBlittable
 {
     private int _intValue;
     private short _shortValue;
     private float _floatValue;
     private double _doubleValue;
 
     #region Properties
     public int IntValue
     {
         get { return _intValue; }
         set { _intValue = value; }
     }
 
     public short ShortValue
     {
         get { return _shortValue; }
         set { _shortValue = value; }
     }
 
     public float FloatValue
     {
         get { return _floatValue; }
         set { _floatValue = value; }
     }
 
     public double DoubleValue
     {
         get { return _doubleValue; }
         set { _doubleValue = value; }
     }
     #endregion
 }


[DllImport(_dllName, CallingConvention = CallingConvention.Cdecl)]
 private extern static void TestStructArgumentByRef(ManagedClassBlittable argClass);

非托管代码可直接修改,定义的方法也无需指定ref修饰符

void __cdecl TestStructArgumentByRef(PSIMPLESTRUCT pStruct)
 {
     ShowNativeStructSize(sizeof(SIMPLESTRUCT));
 
     if( NULL != pStruct)
     {
         // 打?印?初?始?数?据?
         wprintf(L"\n结?构?体?原?数?据?: int = %d, short = %d, float = %f, double = %f\n", 
             pStruct->intValue, pStruct->shortValue, pStruct->floatValue, pStruct->doubleValue);
         
         // 修?改?数?据?
         pStruct->intValue++;
         pStruct->shortValue++;
         pStruct->floatValue += 1;
         pStruct->doubleValue += 1;
     }
 }

测试

ManagedClassBlittable blittableObject = new ManagedClassBlittable();
 blittableObject.IntValue = 1;
 blittableObject.ShortValue = 2;
 blittableObject.FloatValue = 3;
 blittableObject.DoubleValue = 4.5;
 
 TestStructArgumentByRef(blittableObject);
 
 Console.WriteLine("\n结?构?体?新?数?据?:?int = {0}, short = {1}, float = {2:f6}, double = {3:f6}",
     blittableObject.IntValue, blittableObject.ShortValue, blittableObject.FloatValue, blittableObject.DoubleValue);

4.封送为指针

非托管代码

void __cdecl TestReturnStructFromArg(PSIMPLESTRUCT* ppStruct)
 {
     if( NULL != ppStruct)
     {
         *ppStruct = (PSIMPLESTRUCT)CoTaskMemAlloc(
             sizeof(SIMPLESTRUCT));
 
         (*ppStruct)->intValue = 5;
         (*ppStruct)->shortValue = 4;
         (*ppStruct)->floatValue = 3.0;
         (*ppStruct)->doubleValue = 2.1;
     }
     return;
 }


注意这里参数用out修饰符,表示安引用传递参数(ref修饰符对应 [In,Out]属性,out修饰符对应[Out]属性)
[DllImport(_dllName, CallingConvention = CallingConvention.Cdecl)]
 private extern static void TestReturnStructFromArg(out ManagedClassBlittable outObject);

测试

ManagedClassBlittable outObject;
 
  TestReturnStructFromArg(out outObject);
 
  Console.WriteLine("\n结?构?体?新?数?据?:?int = {0}, short = {1}, float = {2:f6}, double = {3:f6}",
      outObject.IntValue, outObject.ShortValue, outObject.FloatValue, outObject.DoubleValue);

封送数组

1.当数组类型为blittable类型时,直接定义即可

定义函数

// int __cdecl TestArrayOfInt(int intArray[], int arraySize);
 [DllImport(_dllName, CallingConvention = CallingConvention.Cdecl)]
 private extern static int TestArrayOfInt(int[] intArray, int arraySize);

// 返回整数数组中,所有元素之和。并将每个元素的值加10
int __cdecl TestArrayOfInt(int intArray[], int arraySize) { int result = 0; for(int i = 0; i < arraySize; i++) { result += intArray[i]; intArray[i] += 10; } return result; }

测试

int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7 };
 Console.WriteLine("\n\n调?用?前?整?数?数?组?元?素?为?:?");
 foreach (int i in intArray)
 {
     Console.Write("{0} ", i);
 }
 
 int sum = TestArrayOfInt(intArray, intArray.Length);
 
 Console.WriteLine("\n调?用?前?整?数?数?组?中?所?有?元?素?之?和?为?:?{0}", sum);
 
 Console.WriteLine("\n调?用?后?整?数?数?组?元?素?为?:?");
 foreach (int i in intArray)
 {
     Console.Write("{0} ", i);
 }
 
 Console.WriteLine();

2.若数组类型为非blittable类型时,则需要显示添加[In,Out]属性

定义函数

// UINT __cdecl TestArrayOfChar(char charArray[], int arraySize);
 [DllImport(_dllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
 private extern static uint TestArrayOfChar([In, Out] char[] charArray, int arraySize);

// 返回字符数组中,数字字符的总数
UINT __cdecl TestArrayOfChar(char charArray[], int arraySize) { int result = 0; for(int i = 0; i < arraySize; i++) { if (isdigit(charArray[i])) { result++; charArray[i] = '@'; } } return result; }

测试代码

char[] charArray = new char[] { 'a', '1', 'b', '2', 'c', '3', '4' };
 Console.WriteLine("\n调?用?前?字?符?数?组?元?素?为?:?");
 foreach (char c in charArray)
 {
     Console.Write("{0} ", c);
 }
 
 uint digitCount = TestArrayOfChar(charArray, charArray.Length);
 
 Console.WriteLine("\n调?用?前?字?符?数?组?中?数?字?个?数?为?:?{0}", digitCount);
 
 Console.WriteLine("\n调?用?后?字?符?数?组?元?素?为?:?");
 foreach (char c in charArray)
 {
     Console.Write("{0} ", c);
 }

3.字符串数组(指针数组)



void __cdecl TestArrayOfString(char* ppStrArray[], int size)
 {
     for(int i = 0; i < size; i++)
     {
         // 翻?转?字?符?串?
         ReverseAnsiStringInPlace(ppStrArray[i]);
     }
 }
 
 void ReverseAnsiStringInPlace(char* rawString)
 {
     int strLength = (int)strlen(rawString);
 
     char tempChar;
     for(int i = 0; i < strLength/2; i++)
     {
         tempChar = rawString[i];
         rawString[i] = rawString[strLength - 1 - i];
         rawString[strLength - 1 - i] = tempChar;
     }
 }


// void __cdecl TestArrayOfString(char* ppStrArray[], int size)
 [DllImport(_dllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
 private extern static void TestArrayOfString([In, Out] string[] charArray, int arraySize);

测试

private static void TestStringArray()
 {
     string[] strings = new string[] { 
         "This is the first string.",
         "Those are brown horse.",
         "The quick brown fox jumps over a lazy dog." };
 
     Console.WriteLine("\n原?始?字?符?串?数?组?中?的?元?素?为?:?");
     foreach (string originalString in strings)
     {
         Console.WriteLine(originalString);
     }
 
     TestArrayOfString(strings, strings.Length);
 
     Console.WriteLine("修?改?后?字?符?串?数?组?中?的?元?素?为?:?");
     foreach (string reversedString in strings)
     {
         Console.WriteLine(reversedString);
     }
 }

若将数组参数指定为IntPtr,则需要注意内存管理

// int __cdecl TestRefArrayOfString(void** strArray, int* size)
 [DllImport(_dllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
 private extern static int TestRefArrayOfString(out IntPtr charArray, out int arraySize);

测试

IntPtr arrayPtr;
 
 int arraySize;
 int returnCount = TestRefArrayOfString(out arrayPtr, out arraySize);
 IntPtr[] arrayPtrs = new IntPtr[returnCount];
 Marshal.Copy(arrayPtr, arrayPtrs, 0, returnCount);
 
 string[] strings = new string[returnCount];
 for (int i = 0; i < returnCount; i++)
 {
     strings[i] = Marshal.PtrToStringUni(arrayPtrs[i]);
        Marshal.FreeCoTaskMem(arrayPtrs[i]);
     Console.WriteLine("#{0}: {1}", i, strings[i]);
 }
 Marshal.FreeCoTaskMem(arrayPtr);