C#与C++数据类型的相互转换。 C#使用非托管的dll,如何封送类,结构体、联合体,不同类型的数组、以及封送回调方法?

本文主要介绍C#与C++数据类型的相互转换。非托管的DLL中包含一些类、结构体、联合体、不同类型的数组,那么将这些数据类型作为返回值,C#将如何接收数据呢?如果是作为传入参数,那么C#又应该怎么样传递这些结构呢?下面就介绍一下。

前提预览

1 VS2019 创建dll并在C# 中调用

2 在引用dll中的DllImport 、StructLayout 、 FieldOffset 、Marshal、和 C++(windows API)对应C#的数据类型

一 、创建一个PInvokeLib.dll

之后的介绍案例许多都是根据PInvokeLib.dll来展开的,我们先打一下地基。
这里不再详细介绍如何制作PInvokeLib.dll,请参考我的另一篇博文:
VS2019 创建dll并在C# 中调用,在这里将PInvokeLib.dll的全部代码贴出来

1.1 PInvokeLib.h文件
#pragma once

#define WIN32_LEAN_AND_MEAN
#include <windows.h>


#ifdef PINVOKELIB_EXPORTS
#define PINVOKELIB_API __declspec(dllexport)
#else
#define PINVOKELIB_API __declspec(dllimport)
#endif


// Define the test structures

typedef struct _MYPOINT
{
    int x;
    int y;
} MYPOINT;

typedef struct _MYPERSON
{
    char* first;
    char* last;
} MYPERSON;

typedef struct _MYPERSON2
{
    MYPERSON* person;
    int age;
} MYPERSON2;

typedef struct _MYPERSON3
{
    MYPERSON person;
    int age;
} MYPERSON3;

typedef struct _MYARRAYSTRUCT
{
    bool flag;
    int vals[3];
} MYARRAYSTRUCT;

union MYUNION
{
    int i;
    double d;
};

union MYUNION2
{
    int i;
    char str[128];
};

typedef struct _MYSTRSTRUCT
{
    wchar_t* buffer;
    UINT size;
} MYSTRSTRUCT;

typedef struct _MYSTRSTRUCT2
{
    char* buffer;
    UINT size;
} MYSTRSTRUCT2;



// constants and pointer definitions

const int COL_DIM = 5;

typedef bool (CALLBACK* FPTR)(int i);

typedef bool (CALLBACK* FPTR2)(char* str);

// Data type codes
enum DataType
{
    DT_I2 = 1,
    DT_I4,
    DT_R4,
    DT_R8,
    DT_STR
};

// This is an exported class.
class PINVOKELIB_API CTestClass
{
public:
    CTestClass(void);
    int DoSomething(int i);

private:
    int m_member;
};

// Exports for PInvokeLib.dll

#ifdef __cplusplus
extern "C"
{
#endif

    PINVOKELIB_API CTestClass* CreateTestClass();

    PINVOKELIB_API void DeleteTestClass(CTestClass* instance);

    PINVOKELIB_API int TestArrayOfInts(int* pArray, int size);

    PINVOKELIB_API int TestRefArrayOfInts(int** ppArray, int* pSize);

    PINVOKELIB_API int TestMatrixOfInts(int pMatrix[][COL_DIM], int row);

    PINVOKELIB_API int TestArrayOfStrings(char* ppStrArray[], int size);

    PINVOKELIB_API int TestArrayOfStructs(MYPOINT* pPointArray, int size);

    PINVOKELIB_API int TestArrayOfStructs2(MYPERSON* pPersonArray, int size);

    PINVOKELIB_API int TestStructInStruct(MYPERSON2* pPerson2);

    PINVOKELIB_API void TestStructInStruct3(MYPERSON3 person3);

    PINVOKELIB_API void TestUnion(MYUNION u, int type);

    PINVOKELIB_API void TestUnion2(MYUNION2 u, int type);

    PINVOKELIB_API void TestCallBack(FPTR pf, int value);

    PINVOKELIB_API void TestCallBack2(FPTR2 pf2, char* value);

    // buffer is an in/out param
    PINVOKELIB_API void TestStringInStruct(MYSTRSTRUCT* pStruct);

    // buffer is in/out param
    PINVOKELIB_API void TestStringInStructAnsi(MYSTRSTRUCT2* pStruct);

    PINVOKELIB_API void TestOutArrayOfStructs(int* pSize, MYSTRSTRUCT2** ppStruct);

    PINVOKELIB_API char* TestStringAsResult();

    PINVOKELIB_API void SetData(DataType typ, void* object);

    PINVOKELIB_API void TestArrayInStruct(MYARRAYSTRUCT* pStruct);

#ifdef __cplusplus
}
#endif




1.2 PInvokeLib.cpp
#include "pch.h"
#include "PInvokeLib.h"
#include <strsafe.h>
#include <objbase.h>
#include <stdio.h>

//******************************************************************
// This is the constructor of a class that has been exported.
CTestClass::CTestClass()
{
    m_member = 1;
}

int CTestClass::DoSomething(int i)
{
    return i * i + m_member;
}

PINVOKELIB_API CTestClass* CreateTestClass()
{
    return new CTestClass();
}

PINVOKELIB_API void DeleteTestClass(CTestClass* instance)
{
    delete instance;
}

//******************************************************************
PINVOKELIB_API int TestArrayOfInts(int* pArray, int size)
{
    int result = 0;

    for (int i = 0; i < size; i++)
    {
        result += pArray[i];
        pArray[i] += 100;
    }
    return result;
}

//******************************************************************
PINVOKELIB_API int TestRefArrayOfInts(int** ppArray, int* pSize)
{
    int result = 0;

    // CoTaskMemAlloc must be used instead of the new operator
    // because code on the managed side will call Marshal.FreeCoTaskMem
    // to free this memory.

    //申请内存空间
    int* newArray = (int*)CoTaskMemAlloc(sizeof(int) * 5);

    for (int i = 0; i < *pSize; i++)
    {
        result += (*ppArray)[i];
    }

    for (int j = 0; j < 5; j++)
    {
        newArray[j] = (*ppArray)[j] + 100;
    }

    //释放
    CoTaskMemFree(*ppArray);
    *ppArray = newArray;
    *pSize = 5;

    return result;
}

//******************************************************************
PINVOKELIB_API int TestMatrixOfInts(int pMatrix[][COL_DIM], int row)
{
    int result = 0;

    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < COL_DIM; j++)
        {
            result += pMatrix[i][j];
            pMatrix[i][j] += 100;
        }
    }
    return result;
}

//******************************************************************
PINVOKELIB_API int TestArrayOfStrings(char* ppStrArray[], int count)
{
    int result = 0;
    STRSAFE_LPSTR temp;
    size_t len;
    const size_t alloc_size = sizeof(char) * 10;

    for (int i = 0; i < count; i++)
    {
        len = 0;
        //确定字符串是否超过指定的长度(以字符为单位)
        StringCchLengthA(ppStrArray[i], STRSAFE_MAX_CCH, &len);
        result += len;

        temp = (STRSAFE_LPSTR)CoTaskMemAlloc(alloc_size);
      //  将一个字符串复制到另一个字符串(temp)
        StringCchCopyA(temp, alloc_size, (STRSAFE_LPCSTR)"123456789");

        // CoTaskMemFree must be used instead of delete to free memory.

        CoTaskMemFree(ppStrArray[i]);
        ppStrArray[i] = (char*)temp;
    }

    return result;
}

//******************************************************************
PINVOKELIB_API int TestArrayOfStructs(MYPOINT* pPointArray, int size)
{
    int result = 0;
    MYPOINT* pCur = pPointArray;

    for (int i = 0; i < size; i++)
    {
        result += pCur->x + pCur->y;
        pCur->y = 0;
        pCur++;
    }

    return result;
}

//******************************************************************
PINVOKELIB_API int TestStructInStruct(MYPERSON2* pPerson2)
{
    size_t len = 0;

    StringCchLengthA(pPerson2->person->last, STRSAFE_MAX_CCH, &len);
    len = sizeof(char) * (len + 2) + 1;

    STRSAFE_LPSTR temp = (STRSAFE_LPSTR)CoTaskMemAlloc(len);
    StringCchCopyA(temp, len, (STRSAFE_LPSTR)"Mc");

    //将一个字符串last连接到另一个字符串temp temp += last;
    StringCbCatA(temp, len, (STRSAFE_LPSTR)pPerson2->person->last);

    CoTaskMemFree(pPerson2->person->last);
    pPerson2->person->last = (char*)temp;

    return pPerson2->age;
}

//******************************************************************
PINVOKELIB_API int TestArrayOfStructs2(MYPERSON* pPersonArray, int size)
{
    int result = 0;
    MYPERSON* pCur = pPersonArray;
    STRSAFE_LPSTR temp;
    size_t len;

    for (int i = 0; i < size; i++)
    {
        len = 0;
        StringCchLengthA(pCur->first, STRSAFE_MAX_CCH, &len);
        len++;
        result += len;
        len = 0;
        StringCchLengthA(pCur->last, STRSAFE_MAX_CCH, &len);
        len++;
        result += len;

        len = sizeof(char) * (len + 2);
        temp = (STRSAFE_LPSTR)CoTaskMemAlloc(len);
        StringCchCopyA(temp, len, (STRSAFE_LPCSTR)"Mc");
        StringCbCatA(temp, len, (STRSAFE_LPCSTR)pCur->last);
        result += 2;

        // CoTaskMemFree must be used instead of delete to free memory.
        CoTaskMemFree(pCur->last);
        pCur->last = (char*)temp;
        pCur++;
    }

    return result;
}

//******************************************************************
PINVOKELIB_API void TestStructInStruct3(MYPERSON3 person3)
{
    printf("\n\nperson passed by value:\n");
    printf("first = %s last = %s age = %i\n\n",
        person3.person.first,
        person3.person.last,
        person3.age);
}

//*********************************************************************
PINVOKELIB_API void TestUnion(MYUNION u, int type)
{
    if ((type != 1) && (type != 2))
    {
        return;
    }
    if (type == 1)
    {
        printf("\n\ninteger passed: %i", u.i);
    }
    else if (type == 2)
    {
        printf("\n\ndouble passed: %f", u.d);
    }
}

//******************************************************************
PINVOKELIB_API void TestUnion2(MYUNION2 u, int type)
{
    if ((type != 1) && (type != 2))
    {
        return;
    }
    if (type == 1)
    {
        printf("\n\ninteger passed: %i", u.i);
    }
    else if (type == 2)
    {
        printf("\n\nstring passed: %s", u.str);
    }
}

//******************************************************************
PINVOKELIB_API void TestCallBack(FPTR pf, int value)
{
    printf("\nReceived value: %i", value);
    printf("\nPassing to callback...");
    bool res = (*pf)(value);

    if (res)
    {
        printf("Callback returned true.\n");
    }
    else
    {
        printf("Callback returned false.\n");
    }
}

//******************************************************************
PINVOKELIB_API void TestCallBack2(FPTR2 pf2, char* value)
{
    printf("\nReceived value: %s", value);
    printf("\nPassing to callback...");
    bool res = (*pf2)(value);

    if (res)
    {
        printf("Callback2 returned true.\n");
    }
    else
    {
        printf("Callback2 returned false.\n");
    }
}

//******************************************************************
PINVOKELIB_API void TestStringInStruct(MYSTRSTRUCT* pStruct)
{
    wprintf(L"\nUnicode buffer content: %s\n", pStruct->buffer);

    // Assuming that the buffer is big enough.
    StringCbCatW(pStruct->buffer, pStruct->size, (STRSAFE_LPWSTR)L"++");
}

//******************************************************************
PINVOKELIB_API void TestStringInStructAnsi(MYSTRSTRUCT2* pStruct)
{
    printf("\nAnsi buffer content: %s\n", pStruct->buffer);

    // Assuming that the buffer is big enough.
    StringCbCatA((STRSAFE_LPSTR)pStruct->buffer, pStruct->size, (STRSAFE_LPSTR)"++");
}

//******************************************************************
PINVOKELIB_API void TestOutArrayOfStructs(int* pSize, MYSTRSTRUCT2** ppStruct)
{
    const int cArraySize = 5;
    *pSize = 0;
    *ppStruct = (MYSTRSTRUCT2*)CoTaskMemAlloc(cArraySize * sizeof(MYSTRSTRUCT2));

    if (ppStruct != NULL)
    {
        MYSTRSTRUCT2* pCurStruct = *ppStruct;
        LPSTR buffer;
        *pSize = cArraySize;

        STRSAFE_LPCSTR teststr = "***";
        size_t len = 0;
        StringCchLengthA(teststr, STRSAFE_MAX_CCH, &len);
        len++;

        for (int i = 0; i < cArraySize; i++, pCurStruct++)
        {
            pCurStruct->size = len;
            buffer = (LPSTR)CoTaskMemAlloc(len);
            StringCchCopyA(buffer, len, teststr);
            pCurStruct->buffer = (char*)buffer;
        }
    }
}

//************************************************************************
PINVOKELIB_API char* TestStringAsResult()
{

    const size_t alloc_size = 64;
    STRSAFE_LPSTR result = (STRSAFE_LPSTR)CoTaskMemAlloc(alloc_size);
    STRSAFE_LPCSTR teststr = "This is return value";
    StringCchCopyA(result, alloc_size, teststr);

    return (char*)result;
}

//************************************************************************
PINVOKELIB_API void SetData(DataType typ, void* object)
{
    switch (typ)
    {
    case DT_I2: printf("Short %i\n", *((short*)object)); break;
    case DT_I4: printf("Long %i\n", *((long*)object)); break;
    case DT_R4: printf("Float %f\n", *((float*)object)); break;
    case DT_R8: printf("Double %f\n", *((double*)object)); break;
    case DT_STR: printf("String %s\n", (char*)object); break;
    default: printf("Unknown type"); break;
    }
}

//************************************************************************
PINVOKELIB_API void TestArrayInStruct(MYARRAYSTRUCT* pStruct)
{
    pStruct->flag = true;
    pStruct->vals[0] += 100;
    pStruct->vals[1] += 100;
    pStruct->vals[2] += 100;
}

官网MSDN的介绍:用平台调用封送数据

二 、创建另一个工程测试PInvokeLib.dll

准备工作:
在这里我新建立一个windform工程名称叫testPInvokeLib,工程建立完成后运行一下,将PInvokeLib.dll复制到exe所在目录。
在这里插入图片描述

2.1 结构体示例

演示了如何传递指向其它结构体的结构体、具有嵌入结构体的结构体和具有嵌入数组的结构体。
看一下PInvokeLib.dll中这些结构体的定义以及对应的导出函数。如下图所示:
C++
在这里插入图片描述
托管的MyPersonMyPerson2MyPerson3MyArrayStruct结构具有以下特征

  • MyPerson 仅包含字符串(char *)成员。
  • MyPerson2 具有指向MyPerson的指针。在.NET中IntPtr类型替换指向非托管结构的原始指针。
  • MyPerson3 将MyPeron作为嵌入结构包含在内。嵌入其它结构的结构可以通过嵌入结构的元素直接放入主结构中来进行平展,还可以保留为嵌入结构。
  • M3yArrayStruct包含整数数组。 MarshalAsAttribute属性将UnmanagedType枚举值设置为ByValArray,此值用于指示数组中的元素个数。
2.1.1 带有指向另一个结构体的结构体 MyPerson2

C++ 原型

typedef struct _MYPERSON
{
    char* first;
    char* last;
} MYPERSON;

typedef struct _MYPERSON2
{
    MYPERSON* person;
    int age;
} MYPERSON2;

PINVOKELIB_API int TestStructInStruct(MYPERSON2* pPerson2)
{
    size_t len = 0;

     //计算last字符串的长度
    StringCchLengthA(pPerson2->person->last, STRSAFE_MAX_CCH, &len);
    len = sizeof(char) * (len + 2) + 1;
    
     //分配内存
    STRSAFE_LPSTR temp = (STRSAFE_LPSTR)CoTaskMemAlloc(len);
    StringCchCopyA(temp, len, (STRSAFE_LPSTR)"Mc");

    //将一个字符串last连接到另一个字符串temp temp += last;
    //即temp  = "Mc" + last 
    StringCbCatA(temp, len, (STRSAFE_LPSTR)pPerson2->person->last);

    //释放内存
    CoTaskMemFree(pPerson2->person->last);
    //指向temp
    pPerson2->person->last = (char*)temp;

    return pPerson2->age;
}

C# 声明原型

 // StructLayout 允许控制内存中类或结构的数据字段的物理布局
 //应用 StructLayoutAttribute 属性以确保成员在内存中按出现的顺序进行排列。

        //为每个非托管(C++)结构声明托管(C#)结构
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public  struct MyPerson
        {
            public string first;
            public string last;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MyPerson2
        {
            //将对 MyPerson2 类型对象的引用(地址的值)传递到非托管代码
            public IntPtr person;
            public int age;
        }

       #region 结构体
        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        //引用传递
        public static extern int TestStructInStruct(ref MyPerson2 person2);
        #endregion

说明一下:
 1、为啥需要LayoutKind.Sequential? 还有CharSet = CharSet.Ansi?
     答:确保成员在内存中按出现的顺序进行排列。
2、为什么C++ 是 `char * first` 到C#变成string first  
3、为什么C++中的 MyPerson* person在C#中用的是 IntPtr person?
  答:请看下图的说明

不同平台调用数据类型的对应关系
在这里插入图片描述

C# 调用函数

  //包含带有指向另一结构体指针的结构体
            MyPerson personName;
            personName.first = "Mark";
            personName.last = "Lee";

            //  MyPerson2 内中包含MyPerson
            MyPerson2 personAll;
            personAll.age = 30;

            //IntPtr 类型替换指向非托管结构(如C++)的原始指针
            // AllocCoTaskMem 返回personName的地址(指针)
            IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(personName));

            //StructureToPtr 托管结构的内容(personName)复制到非托管的缓冲区(buffer)
            Marshal.StructureToPtr(personName, buffer, false);
            personAll.person = buffer;

            Console.WriteLine("\nPerson before call: ");
            Console.WriteLine("first = {0}, last = {1}, age = {2}",
                personName.first, personName.last, personAll.age);

            //对 MyPerson2 类型对象的引用(地址的值)传递到非托管(C++)代码
            int res = TestStructInStruct(ref personAll);


            // PtrToStructure  将非托管缓冲区的数据封送到托管对象
            MyPerson personRes = (MyPerson)Marshal.PtrToStructure(personAll.person,
                typeof(MyPerson));

            //FreeCoTaskMem 方法释放非托管(C++)的内存块
            Marshal.FreeCoTaskMem(buffer);

            Console.WriteLine("\nPerson after call: ");
            Console.WriteLine("first = {0}, last = {1}, age = {2}",
            personRes.first, personRes.last, personAll.age);

运行结果

Person before call: 
first = Mark, last = Lee, age = 30

Person after call: 
first = Mark, last = McLee, age = 30
2.1.2 嵌套的结构体

C++ 原型

typedef struct _MYPERSON3
{
    MYPERSON person;
    int age;
} MYPERSON3;
PINVOKELIB_API void TestStructInStruct3(MYPERSON3 person3)
{
    printf("\n\nperson passed by value:\n");
    printf("first = %s last = %s age = %i\n\n",
        person3.person.first,
        person3.person.last,
        person3.age);
}

C#声明原型

  [StructLayout(LayoutKind.Sequential)]
        public struct MyPerson3
        {
            public MyPerson person;
            public int age;
        }

 [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
     //值传递
    public static extern int TestStructInStruct3( MyPerson3 person3);

C#调用函数

  //带有嵌套结构体的结构体  即结构体A包含结构体B
            MyPerson3 person3 = new MyPerson3();
            person3.person.first = "John";
            person3.person.last = "Evans";
            person3.age = 27;
            TestStructInStruct3(person3);

运行结果

person passed by value:
first = John last = Evans age = 27
2.1.3 含有数组的结构体

C++原型

typedef struct _MYARRAYSTRUCT
{
    bool flag;
    int vals[3];
} MYARRAYSTRUCT;

PINVOKELIB_API void TestArrayInStruct(MYARRAYSTRUCT* pStruct)
{
    pStruct->flag = true;
    pStruct->vals[0] += 100;
    pStruct->vals[1] += 100;
    pStruct->vals[2] += 100;
}

C#声明原型

 [StructLayout(LayoutKind.Sequential)]
        public struct MyArrayStruct
        {
            public bool flag;

            //用于指示数组中的元素个数
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
            public int[] vals;
        }

  [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
   //引用传递
  public static extern int TestArrayInStruct(ref MyArrayStruct myStruct);

MyArrayStruct包含整数数组。 MarshalAsAttribute属性将UnmanagedType枚举值设置为ByValArray,此值用于指示数组中的元素个数

C#调用函数

 //带有数组的结构体
            MyArrayStruct myStruct = new MyArrayStruct();

            myStruct.flag = false;
            myStruct.vals = new int[3];
            myStruct.vals[0] = 1;
            myStruct.vals[1] = 4;
            myStruct.vals[2] = 9;

            Console.WriteLine("\nStructture with array before call: ");
            Console.WriteLine(myStruct.flag);
            Console.WriteLine("{0}  {1}  {2}", myStruct.vals[0],
                myStruct.vals[1], myStruct.vals[2]);
           
             //引用传递        
             TestArrayInStruct(ref myStruct);

            Console.WriteLine("\nStructture with array after call: ");
            Console.WriteLine(myStruct.flag);
            Console.WriteLine("{0}  {1}  {2}", myStruct.vals[0],
                myStruct.vals[1], myStruct.vals[2]);

运行结果

Structture with array before call: 
False
1  4  9

Structture with array after call: 
True
101  104  109
2.2 联合体示例

使用PInvokeLib.dll演示了如何将仅包含值类型的结构以及包含值类型和字符串类型的结构作为参数传递至需要联合的非托管函数。联合体是可由两个或多个变量共享的内存位置。(即联合体的每个变量指向的是同一块内存,共享内存位置)

C++原型

union MYUNION
{
    int i;
    double d;
};

union MYUNION2
{
    int i;
    char str[128];
};


PINVOKELIB_API void TestUnion(MYUNION u, int type)
{
    if ((type != 1) && (type != 2))
    {
        return;
    }
    if (type == 1)
    {
        printf("\n\ninteger passed: %i", u.i);
    }
    else if (type == 2)
    {
        printf("\n\ndouble passed: %f", u.d);
    }
}

//******************************************************************
PINVOKELIB_API void TestUnion2(MYUNION2 u, int type)
{
    if ((type != 1) && (type != 2))
    {
        return;
    }
    if (type == 1)
    {
        printf("\n\ninteger passed: %i", u.i);
    }
    else if (type == 2)
    {
        printf("\n\nstring passed: %s", u.str);
    }
}

C# 声明原型

        //联合体 声明原型
        [StructLayout(LayoutKind.Explicit)]
        public struct MyUnion
        {
            [FieldOffset(0)]
            public int i;
            [FieldOffset(0)]
            public double d;
        }


        [StructLayout(LayoutKind.Explicit, Size = 128)]
        public struct MyUnion2_1
        {
            [FieldOffset(0)]
            public int i;
        }


        [StructLayout(LayoutKind.Sequential)]
        public struct MyUnion2_2
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string str;
        }

       #region 联合体
        [DllImport("PInvokeLib.dll")]
        internal static extern void TestUnion(MyUnion u, int type);

        [DllImport("PInvokeLib.dll")]
        internal static extern void TestUnion2(MyUnion2_1 u, int type);

        [DllImport("PInvokeLib.dll")]
        internal static extern void TestUnion2(MyUnion2_2 u, int type);
        #endregion

在托管代码(C#)中,将联合体定义为结构体。

说明:      MyUnion包含两个值类型(int 和 double),将其作为成员。
请注意,这两个成员具有相同的偏移值,
因此使用 StructLayoutAttribute 属性设置为控制每个数据成员的精确位置。 
FieldOffsetAttribute 属性提供联合的非托管表示形式中字段的物理位置

在托管代码(C#)中,值类型和引用类型不允许重叠

在C#中为什么需要将MyUnion2拆分成两个?
1、 MyUnion2_1 和 MyUnion2_2分别包含值类型(int)和字符串。在托管代码(C#)中,值类型和引用类型不允许重叠。

C# 调用函数

            //联合体示例
            MyUnion mu = new MyUnion();
            mu.i = 99;
            TestUnion(mu, 1);

            mu.d = 99.99;
            TestUnion(mu, 2);

            MyUnion2_1 mu2_1 = new MyUnion2_1();
            mu2_1.i = 99;
            TestUnion2(mu2_1, 1);


            MyUnion2_2 mu2_2 = new MyUnion2_2();
            mu2_2.str = "*** string  ***";
            TestUnion2(mu2_2, 2);
      

运行结果:

integer passed: 99

double passed: 99.990000

integer passed: 99

string passed: *** string  ***
2.3 不同类型的数组

本小节举例如何传递一下类型的数组:

  • 通过值传递的整数数组。                    将整数的数组作为 In 参数进行传递。
    
  • 通过引用传递的整数数组(可以调整大小)。     将整数的数组作为 In/Out 参数进行传递。
    
  • 通过值传递的整数多维数组。                   将整数的矩阵作为 In 参数进行传递。
    
  • 通过值传递的字符串数组。                     将字符串的数组作为 In 参数进行传递。
    
  • 传递具有整数的结构数组。                    将包含整数的结构数组作为 In 参数进行传递。
    
  • 传递具有字符串的结构数组。          将仅包含字符串的结构数组作为 In/Out 参数进行传递。 可以更改数组成员。
    

C++ 原型

typedef struct _MYPOINT
{
    int x;
    int y;
} MYPOINT;

typedef struct _MYPERSON
{
    char* first;
    char* last;
} MYPERSON;

C# 声明原型

  [StructLayout(LayoutKind.Sequential)]
        public struct MyPoint
        {
            public int x;
            public int y;

            public MyPoint(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public  struct MyPerson1
        {
            public string First;
            public string Last;

            public MyPerson1(string first, string last)
            {
                this.First = first;
                this.Last = last;
            }
        }

随后的例子用的都是上面定义的结构类型。

2.3.1 通过值传递的整数数组

从 PInvokeLib.dll 导出的TestArrayOfInts
C++ 函数原型

数组成员再加100

PINVOKELIB_API int TestArrayOfInts(int* pArray, int size)
{
    int result = 0;

    for (int i = 0; i < size; i++)
    {
        result += pArray[i];
        pArray[i] += 100;
    }
    return result;
}

C# 声明原型

 // 为按值排列的整数数组声明托管原型
 // 数组的大小不能改变,但是数组会被复制回来。
 [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int TestArrayOfInts(
            [In, Out] int[] array, int size);

C# 函数调用

  //array ByVal 值传递
            int[] array1 = new int[10];
            Console.WriteLine(" Interger array passed ByVal before call:");
            for(int i=0; i< array1.Length; i++)
            {
                array1[i] = i;
                Console.Write("  " + array1[i]);
            }
            int sum1 = TestArrayOfInts(array1, array1.Length);
             Console.WriteLine("\nSum1 of elements: " + sum1);
            Console.WriteLine("\nInterger array passed ByVal after call:");
            foreach(int i in array1)
            {
               
                Console.Write("  " + i);
            }

运行结果:

 Interger array passed ByVal before call:
  0  1  2  3  4  5  6  7  8  9
Sum1 of elements: 45

Interger array passed ByVal after call:
  100  101  102  103  104  105  106  107  108  109
2.3.2 通过引用传递的整数数组(可以调整大小)

从 PInvokeLib.dll 导出的TestRefArrayOfInts

C++ 函数原型

将数值大小变成5,且每个数组元素再加100

PINVOKELIB_API int TestRefArrayOfInts(int** ppArray, int* pSize)
{
    int result = 0;

    //必须使用CoTaskMemAlloc而不是new操作符
    //因为托管端的代码会调用Marshal.FreeCoTaskMem
    //释放此内存。
    // 
    //申请内存空间
    int* newArray = (int*)CoTaskMemAlloc(sizeof(int) * 5);

    for (int i = 0; i < *pSize; i++)
    {
        result += (*ppArray)[i];
    }

    for (int j = 0; j < 5; j++)
    {
        newArray[j] = (*ppArray)[j] + 100;
    }

    //释放
    CoTaskMemFree(*ppArray);
    *ppArray = newArray;
    *pSize = 5;

    return result;
}

C# 声明原型

        // 通过引用为整数数组声明托管原型
        // 数组大小可以改变,但数组不会自动复制回来,因为编译器不知道结果大小
        // 需要手动执行复制
        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int TestRefArrayOfInts(
            ref IntPtr array, ref int size);

C#调用函数

 //array ByRef 引用传递
            int[] array2 = new int[10];
            int size = array2.Length;
            Console.WriteLine("\n\nInteger array passed ByRef before call:");
            for(int i =0; i<array2.Length; i++)
            {
                array2[i] = i;
                Console.Write("  " + array2[i]);
            }

            //分配内存大小
            IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(size) * array2.Length);
            //将array2数组复制到非托管内存指针buffer
            Marshal.Copy(array2, 0, buffer, array2.Length);

            int sum2 = TestRefArrayOfInts(ref buffer, ref size);
            Console.WriteLine("\nSum of elements: " + sum2);

            if(size > 0)
            {
                int[] arrayRes = new int[size];
                //将非托管内存中的指针buffer复制到托管32位有符号整数数组arrayRes中
                Marshal.Copy(buffer, arrayRes, 0, size);

                //释放由非托管COM口任务内存分配器分配的内存块
                Marshal.FreeCoTaskMem(buffer);

                Console.WriteLine("\n\nInteger array passed ByRef after call:");
              foreach(int i in arrayRes)
                {
                    Console.Write("  " + i);
                }
            }else
            {
                Console.WriteLine("\nArray after call is empty");
            }

运行结果:

Integer array passed ByRef before call:
  0  1  2  3  4  5  6  7  8  9
Sum of elements: 45


Integer array passed ByRef after call:
  100  101  102  103  104
2.3.3 通过值传递的整数多维数组

从 PInvokeLib.dll 导出的TestMatrixOfInts

C++ 函数原型

将多维数组中每个数组元素再加100,返回原数组元素之和

PINVOKELIB_API int TestMatrixOfInts(int pMatrix[][COL_DIM], int row)
{
    int result = 0;

    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < COL_DIM; j++)
        {
            result += pMatrix[i][j];
            pMatrix[i][j] += 100;
        }
    }
    return result;
}

C# 声明原型

         // 为按值的整数矩阵声明托管原型。
        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int TestMatrixOfInts(
            [In, Out] int[,] pMatrix, int row);

C#调用函数

 // matrix  ByVal  多维数组,通过值传递
            const int DIM = 5;
            int[,] matrix = new int[DIM, DIM];

            Console.WriteLine("\n\nMatrix before call: ");
            for(int i=0; i<DIM; i++)
            {
                for(int j=0; j<DIM; j++)
                {
                    matrix[i, j] = j;
                    Console.Write("  " + matrix[i, j]);
                }
                Console.WriteLine("");
            }

            //调用函数
            int sum3 = TestMatrixOfInts(matrix, DIM);
            Console.WriteLine("\nSum of element: " + sum3);
            Console.WriteLine("\nMatrix after call: ");
            for(int i=0;i < DIM; i++)
            {
                for(int j=0; j<DIM; j++)
                {
                    Console.Write("  " + matrix[i, j]);
                }
                Console.WriteLine("");
            }

运行结果:

Matrix before call: 
  0  1  2  3  4
  0  1  2  3  4
  0  1  2  3  4
  0  1  2  3  4
  0  1  2  3  4

Sum of element: 50

Matrix after call: 
  100  101  102  103  104
  100  101  102  103  104
  100  101  102  103  104
  100  101  102  103  104
  100  101  102  103  104

2.3.4 通过值传递的字符串数组

从 PInvokeLib.dll 导出的TestArrayOfStrings
C++ 函数原型

将字符串数组的每个元素更改为"123456789",并返回原字符串数组中每个字符串元素长度的总和。

PINVOKELIB_API int TestArrayOfStrings(char* ppStrArray[], int count)
{
    int result = 0;
    STRSAFE_LPSTR temp;
    size_t len;
    const size_t alloc_size = sizeof(char) * 10;

    for (int i = 0; i < count; i++)
    {
        len = 0;
        //确定字符串是否超过指定的长度(以字符为单位)
        StringCchLengthA(ppStrArray[i], STRSAFE_MAX_CCH, &len);
        result += len;

        temp = (STRSAFE_LPSTR)CoTaskMemAlloc(alloc_size);
      //  将一个字符串复制到另一个字符串(temp)
        StringCchCopyA(temp, alloc_size, (STRSAFE_LPCSTR)"123456789");

        // CoTaskMemFree must be used instead of delete to free memory.

        CoTaskMemFree(ppStrArray[i]);
        ppStrArray[i] = (char*)temp;
    }

    return result;
}

C# 声明原型

  // 按值声明字符串数组的托管原型。
        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int TestArrayOfStrings(
            [In, Out] string[] stringArray, int size);

C#调用函数

           // string array ByVal  通过值传递的字符串数组
            string[] strArray = { "one", "two", "three", "four", "five" };
            Console.WriteLine("\n\nstring array before call:");
            foreach(string s in  strArray)
            {
                Console.Write(" " + s);
            }

             //调用函数
            int lenSum = TestArrayOfStrings(strArray, strArray.Length);
            Console.WriteLine("\nSum of string lengths: " + lenSum);
            Console.WriteLine("\n\nstring array after call:");
            foreach(string s in strArray)
            {
                Console.Write(" " + s);

            }

运行结果:

string array before call:
 one two three four five
Sum of string lengths: 19


string array after call:
 123456789 123456789 123456789 123456789 123456789
2.3.5 通过值传递的整数多维数组

从 PInvokeLib.dll 导出的TestArrayOfStructs
C++ 函数原型

将y变为0,返回数组中每个元素相加的和。

PINVOKELIB_API int TestArrayOfStructs(MYPOINT* pPointArray, int size)
{
    int result = 0;
    MYPOINT* pCur = pPointArray;

    for (int i = 0; i < size; i++)
    {
        result += pCur->x + pCur->y;
        pCur->y = 0;
        pCur++;
    }

    return result;
}

C# 声明原型

 // 声明具有整数数组的结构的托管原型。
        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int TestArrayOfStructs(
            [In, Out] MyPoint[] pointArray, int size);

C#调用函数

  //struct array ByVal 结构体数组 传递值
            MyPoint[] points = { new MyPoint(1, 1), new MyPoint(2, 2), new MyPoint(3, 3) };
            Console.WriteLine("\n\nPoints array before call: ");
            foreach(MyPoint p in points)
            {
                Console.WriteLine($" x = {p.x}, y = {p.y}");
            }

            //调用函数
            int allSum = TestArrayOfStructs(points, points.Length);
            Console.WriteLine("\nSum of points: " + allSum);
            Console.WriteLine("\nPoints array after call: ");
            foreach(MyPoint p in points)
            {
                Console.WriteLine($"x = {p.x}, y = {p.y}");
            }

运行结果:

Points array before call: 
 x = 1, y = 1
 x = 2, y = 2
 x = 3, y = 3

Sum of points: 12

Points array after call: 
x = 1, y = 0
x = 2, y = 0
x = 3, y = 0
2.3.6 传递具有字符串的结构数组

从 PInvokeLib.dll 导出的TestArrayOfStructs2
C++ 函数原型

将Last字符串前面加上“Mc”,返回数组中字符串的总长度

PINVOKELIB_API int TestArrayOfStructs2(MYPERSON* pPersonArray, int size)
{
    int result = 0;
    MYPERSON* pCur = pPersonArray;
    STRSAFE_LPSTR temp;
    size_t len;

    for (int i = 0; i < size; i++)
    {
        len = 0;
        //计算first字符串的长度
        StringCchLengthA(pCur->first, STRSAFE_MAX_CCH, &len);
        len++;
        result += len;
        len = 0;
        //计算last字符串的长度
        StringCchLengthA(pCur->last, STRSAFE_MAX_CCH, &len);
        len++;
        result += len;

        len = sizeof(char) * (len + 2);
        //分配内存块
        temp = (STRSAFE_LPSTR)CoTaskMemAlloc(len);
        //temp  = "Mc"
        StringCchCopyA(temp, len, (STRSAFE_LPCSTR)"Mc");
        // temp = "Mc" + last
        StringCbCatA(temp, len, (STRSAFE_LPCSTR)pCur->last);
        result += 2;

        // CoTaskMemFree must be used instead of delete to free memory.
        //释放last的内存块
        CoTaskMemFree(pCur->last);
        //指向temp的内存区域
        pCur->last = (char*)temp;
        pCur++;
    }

    return result;
}

C# 声明原型

  // 声明具有字符串的结构数组的托管原型。
        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int TestArrayOfStructs2(
            [In, Out] MyPerson1[] personArray, int size);

C#调用函数

   // struct with strings array ByVal 字符串结构体数组 传递值
            MyPerson1[] persons =
            {
                new MyPerson1("Kim", "Akers"),
                new MyPerson1("Adam", "Barr"),
                new MyPerson1("Jo", "Brown")
            };

            Console.WriteLine("\n\nPersons array before call:");
            foreach(MyPerson1 pe in persons)
            {
                Console.WriteLine($"First = {pe.First}, Last = {pe.Last}");
            }

            int namesSum = TestArrayOfStructs2(persons, persons.Length);
            Console.WriteLine("\nSum of name lengths: " + namesSum);
            Console.WriteLine("\n\nPersons array after call: ");
            foreach(MyPerson1 pe in persons)
            {
                Console.WriteLine($"First = {pe.First}, Last = {pe.Last}");
            }

运行结果:

Persons array before call:
First = Kim, Last = Akers
First = Adam, Last = Barr
First = Jo, Last = Brown

Sum of name lengths: 35


Persons array after call: 
First = Kim, Last = McAkers
First = Adam, Last = McBarr
First = Jo, Last = McBrown

MSDN官网的描述 封送不同类型的数组

2.3.7 按引用传递包含整数和字符串的结构数组

举例说明如何将包含整数和字符串的结构数组作为Out参数传递到非托管函数。
通过两种方法来实现,一个种是使用Marshal类,另外一种是使用不安全代码调用本机函数。
TestOutArrayOfStructs从从 PinvokeLib.dll 导出。
C++原型

typedef struct _MYSTRSTRUCT2
{
    char* buffer;
    UINT size;
} MYSTRSTRUCT2;

PINVOKELIB_API void TestOutArrayOfStructs(int* pSize, MYSTRSTRUCT2** ppStruct)
{
    const int cArraySize = 5;
    *pSize = 0;
    *ppStruct = (MYSTRSTRUCT2*)CoTaskMemAlloc(cArraySize * sizeof(MYSTRSTRUCT2));

    if (ppStruct != NULL)
    {
        MYSTRSTRUCT2* pCurStruct = *ppStruct;
        LPSTR buffer;
        *pSize = cArraySize;

        STRSAFE_LPCSTR teststr = "***";
        size_t len = 0;
        StringCchLengthA(teststr, STRSAFE_MAX_CCH, &len);
        len++;

        for (int i = 0; i < cArraySize; i++, pCurStruct++)
        {
            pCurStruct->size = len;
            buffer = (LPSTR)CoTaskMemAlloc(len);
            StringCchCopyA(buffer, len, teststr);
            pCurStruct->buffer = (char*)buffer;
        }
    }
}

C# 声明原型(方法1 Marshal)

  //包含整数和字符串的结构数组作为 Out 参数传递到非托管函数
        [StructLayout(LayoutKind.Sequential ,CharSet = CharSet.Ansi)]
        public class MyStruct
        {
            public string buffer;
            public int size;
        }
   
请注意这里的MyStruct使用的是class 而不是struct, 因为使用struct运行时会报错。

出现的问题是:  Marshal.PtrToStructure(curent, manArray[i]); 此结构不得为值类
解决方法:需要将Structure(结构)重新定义为Class(类)可解决

C# 调用函数 (方法1 Marshal)

         public static void UsingMarshaling()
        {
            int size;
            IntPtr outArray;

            //调用函数
            TestOutArrayOfStructs(out size, out outArray);
            MyStruct[] manArray = new MyStruct[size];
            IntPtr curent = outArray;

            for(int i=0;i <size; i++)
            {
                manArray[i] = new MyStruct();

                //将数据从非托管内存块(curent)封送到托管对象(manArray[i])
                Marshal.PtrToStructure(curent, manArray[i]);

                //释放指定的非托管内存块(curent)所指向的所有子结构
                Marshal.DestroyStructure(curent, typeof(MyStruct));
                curent = (IntPtr)((long)curent + Marshal.SizeOf(manArray[i]));

                Console.WriteLine("Element {0}: {1}  {2}", i, manArray[i].buffer, manArray[i].size);

            }
            //释放由非托管分配的内存块
            Marshal.FreeCoTaskMem(outArray);

        }

C# 声明原型(方法2 使用不安全代码)

   //声明带一个指针的结构体
        [StructLayout(LayoutKind.Sequential)]
        public struct MyUnsafeStruct
        {
            public IntPtr buffer;
            public int size;
        }

C# 调用函数(方法2 使用不安全代码)
请注意在托管代码中是不允许使用指针的,要想使用指针,请设置工程的属性【允许不安全代码】将其打勾,才可以使用指针。
在这里插入图片描述

  public static unsafe void UsingUnsafePointer()
        {
            int size;
            MyUnsafeStruct* pResut;

            TestOutArrayOfStructs(out size, &pResut);
            MyUnsafeStruct* pCurrent = pResut;
            
            for(int i=0; i<size; i++, pCurrent++)
            {
                Console.WriteLine("Element {0}: {1}  {2}", i,
                    Marshal.PtrToStringAnsi(pCurrent->buffer),
                    pCurrent->size);

                //释放内存
                Marshal.FreeCoTaskMem((IntPtr)pResut);
            }
        }

运行结果
可以看到两种方法的运行结果是一样的

Using marshal class

Element 0: ***  4
Element 1: ***  4
Element 2: ***  4
Element 3: ***  4
Element 4: ***  4

Using unsafe class

Element 0: ***  4
Element 1: ***  4
Element 2: ***  4
Element 3: ***  4
Element 4: ***  4
2.4 将委托作为回调方法进行封送

本案例演示如何将委托传递给需要函数指针的非托管函数。委托是可以容纳方法引用的类,等效于类型安全函数或回调函数。
TestCallBack 以及 TestCallBack 2 从 PinvokeLib.dll 导出。
C++ 原型

typedef bool (CALLBACK* FPTR)(int i);

typedef bool (CALLBACK* FPTR2)(char* str);

PINVOKELIB_API void TestCallBack(FPTR pf, int value)
{
    printf("\nReceived value: %i", value);
    printf("\nPassing to callback...");
    bool res = (*pf)(value);

    if (res)
    {
        printf("Callback returned true.\n");
    }
    else
    {
        printf("Callback returned false.\n");
    }
}

//******************************************************************
PINVOKELIB_API void TestCallBack2(FPTR2 pf2, char* value)
{
    printf("\nReceived value: %s", value);
    printf("\nPassing to callback...");
    bool res = (*pf2)(value);

    if (res)
    {
        printf("Callback2 returned true.\n");
    }
    else
    {
        printf("Callback2 returned false.\n");
    }
}

C# 声明原型

        public delegate bool FPtr(int value);
        public delegate bool FPtr2(string value);
        
        #region 将委托作为回调方法进行封送
        // Declares managed prototypes for unmanaged functions.
        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void TestCallBack(FPtr cb, int value);

        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void TestCallBack2(FPtr2 cb2, string value);
        #endregion

注意:委托的签名必须匹配它引用的方法的签名。 例如,FPtr 和 FPtr2委托的签名与 DoSomething 和 DoSomething2 方法相同。

C# 调用函数

      //将委托作为回调方法进行封送
        public static void CallBack()
        {
            FPtr cb = new FPtr(Form1.DoSomething);
            TestCallBack(cb, 99);

            FPtr2 cb2 = new FPtr2(Form1.DoSomething2);
            TestCallBack2(cb2, "abc");
        }

    public static bool DoSomething(int value)
    {
        Console.WriteLine($"\nCallback called with param: {value}");
        // ...
        return true;
    }

    public static bool DoSomething2(string value)
    {
        Console.WriteLine($"\nCallback called with param: {value}");
        // ...
        return true;
    }

运行结果

Callback1 called with param: 99

Callback2 called with param: abc

Received value: 99
Passing to callback...Callback returned true.

Received value: abc
Passing to callback...Callback2 returned true.

MSDN官网的描述 将委托作为回调方法进行封送

2.5 封送类

案例一 : 从PInvokeLib.dll导出的类CTestClass。
演示何如获得类CTestClass,且调用类中的public函数。
C++原型

//.h文件
// This is an exported class.
class PINVOKELIB_API CTestClass
{
public:
    CTestClass(void);
    int DoSomething(int i);

private:
    int m_member;
};


//.cpp文件
// This is the constructor of a class that has been exported.
CTestClass::CTestClass()
{
    m_member = 1;
}

int CTestClass::DoSomething(int i)
{
    return i * i + m_member;
}

PINVOKELIB_API CTestClass* CreateTestClass()
{
    return new CTestClass();
}

PINVOKELIB_API void DeleteTestClass(CTestClass* instance)
{
    delete instance;
}

C# 声明原型

        #region  声明类的原型
        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr CreateTestClass();

        [DllImport("PInvokeLib.dll", EntryPoint = "?DoSomething@CTestClass@@QAEHH@Z",
            CallingConvention = CallingConvention.ThisCall)]
        public static extern int TestThisCalling(IntPtr ths, int i);

        [DllImport("PInvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void  DeleteTestClass(IntPtr ths);
        #endregion

C#调用函数

       //调用类
        public static void UsingClass()
        {
            //获取句柄
            IntPtr cTestClass = CreateTestClass();
            //调用函数
            int res = TestThisCalling(cTestClass, 9);
            Console.WriteLine("\nResult: {0} \n" , res);
            //释放
            DeleteTestClass(cTestClass);
        }

运行结果

// 9 * 9 + 1 = 82
Result: 82 

案例一 在MSDN官网的介绍

案例二 演示如何将指向类的指针传递到需要指向结构的指针的非托管函数

  • 从 Kernel32.dll 导出的 GetSystemTime
    C++ 原型
VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);

typedef struct _SYSTEMTIME {
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;

C#代码示例

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public class SystemTime
{
    public ushort year;
    public ushort month;
    public ushort weekday;
    public ushort day;
    public ushort hour;
    public ushort minute;
    public ushort second;
    public ushort millisecond;
}

internal static class NativeMethods
{
    // Declares a managed prototype for the unmanaged function using Platform Invoke.
    [DllImport("Kernel32.dll")]
    internal static extern void GetSystemTime([In, Out] SystemTime st);
}

public class App
{
    public static void Main()
    {
        Console.WriteLine("C# SysTime Sample using Platform Invoke");
        SystemTime st = new SystemTime();
        NativeMethods.GetSystemTime(st);
        Console.Write("The Date is: ");
        Console.Write($"{st.month} {st.day} {st.year}");
    }
}

// The program produces output similar to the following:
//
// C# SysTime Sample using Platform Invoke
// The Date is: 11 01 2022

MSDN详细介绍

2.6 封送枚举类型(以及void* 指针)

下面的例子介绍了如何将数据传递给void指针作为参数的非托管函数。这里提供了两种方法。首先看一下C++的原型代码,从PInvokeLib.dll导出的SetData函数。
C++原型

enum DataType
{
    DT_I2 = 1,
    DT_I4,
    DT_R4,
    DT_R8,
    DT_STR
};


PINVOKELIB_API void SetData(DataType typ, void* object)
{
    switch (typ)
    {
    case DT_I2: printf("Short %i\n", *((short*)object)); break;
    case DT_I4: printf("Long %i\n", *((long*)object)); break;
    case DT_R4: printf("Float %f\n", *((float*)object)); break;
    case DT_R8: printf("Double %f\n", *((double*)object)); break;
    case DT_STR: printf("String %s\n", (char*)object); break;
    default: printf("Unknown type"); break;
    }
}

来看一下C#中对应声明的原型,这里列举了两种托管原型方法:SetData和SetData2.
C# 声明原型

         public  enum DataType: byte
        {
            DT_I2 = 1,
            DT_I4,
            DT_R4,
            DT_R8,
            DT_STR
        };
       
        [DllImport("PInvokeLib.dll")]
        //当需要传递void*时使用AsAny
        internal static extern void SetData(DataType typ,[MarshalAs(UnmanagedType.AsAny)] Object O);

        //当使用void*时,重载一下该函数

        [DllImport("PInvokeLib.dll", EntryPoint = "SetData")]
        public static extern void SetData2(DataType type, ref double data);

        [DllImport("PInvokeLib.dll", EntryPoint = "SetData")]
        public static extern void SetData2(DataType type, string str);

说明一下:
1、使用了MarshalAs(UnmanagedType.AsAny),主要是使得运行时确定对象的类型并将该对象作为该类型进行封送处理。     
       MarshalAs:枚举成员初始化。
       AsAny:  一个动态类型,将在运行时确定对象的类型,并将该对象作为所确定的类型进行封送处理

2、 重载的SetData2声明DataType枚举并标识双精度类型或字符串类型。ref关键字通过引用传递此双精度类型。

3、 方法1指定每个枚举元素,方法2仅指定最大的值类型和字符串。

C# 调用函数


        //枚举类型 加void*
        public static void UsingEnum()
        {
          
            //方法1 指定每个枚举元素
            SetData(DataType.DT_I2, (short)12);

            SetData(DataType.DT_I4, (long)99999);

            SetData(DataType.DT_R4, (float)99.87);
           
            SetData(DataType.DT_R8, (double)1.2345);
            SetData2(DataType.DT_STR, "hello");

            //方法2 仅指定最大长度的值类型和字符串
            double d = 3.14159;

            SetData2(DataType.DT_R8, ref d);
            SetData2(DataType.DT_STR, "abcd");
        }

运行结果

Short 12
Long 99999
Float 99.870003
Double 1.234500
String hello
Double 3.141590
String abcd

官网MSDN的介绍 void*示例

三 、总结

在这里插入图片描述
平台调用非托管函数时,将执行以下操作序列:

1、定位包含函数的 DLL
2、将 DLL 加载到内存中。
3、在内存中定位函数的地址并将其参数推送到堆栈上,从而根据需要封送数据。
4、将控制转移到非托管函数。

如果要调用导出的DLL函数

1 标识DLL中的函数:至少必须指定函数名称及其内含的DLL的名称。

2 创建用于容纳DLL函数的类。 可使用现有的类为每个托管函数的创建单独的类

3 在托管代码中创建原型。
   [C#] 使用 DllImportAttribute 标识 DLL 和函数。 为此方法标记 static 和 extern 修饰符 。
   [C++] 使用 DllImportAttribute 标识 DLL 和函数。 用 extern "C" 标记此包装方法或函数。

4 调用DLL函数。  与调用任何其它托管方法一样,在托管类上调用方法。
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值