P/Invoke的全称是Platform Invoke (平台调用),它实际上是一种函数调用机制通 过P/Invoke我们就可以调用非托管DLL中的函数。
P/Invoke依次执行以下操作:

  • 查找包含该函数的非托管DLL
  • 将该非托管DLL加载到内存中
  • 查找函数在内存中的地址并将其参数按照函数的调用约定压栈
  • 将控制权转移给非托管函数

P/Invoke调用示意图

由于项目需要用到了C#代码调用C++的动态库,期间遇到了参数封装与传送的问题,记录于此。

  • 对于不对称的结构体数组,最好定义紧凑型排列以避免编译器自动做字节对齐。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi, Pack=1)]
public struct SRawStockDict
{
/// UINT32->unsigned int
public uint m_dwStockID;
/// char
public byte m_cMarket;
/// char
public byte m_cType;
/// char[23]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 23)]
public string m_cCode;
/// char[23]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 23)]
public string m_cName;
/// char[31]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 31)]
public string m_cNameEx;
/// char
public byte m_cFlag;
/// UINT32->unsigned int
public uint m_dwObjID;
/// char
public byte m_cOptType;
}

如上,若不加Pack=1。那么用Marshal.Sizeof(typeof(SRawStockDict))获取到的大小是92。加上Pack=1之后得到的大小就是89,这样在传入被调用的C++函数中不会出错。

  • 若导出的C++函数中有指向结构体数组的指针,并且该参数既会作为输入也会作为输出。那么类型匹配如:
1
2
3
4
C++
SRawStockDict *pNew
C#
[In, Out] SRawStockDict[] pNew

对于SRawStockDict *&pNew 这种特殊的情况 ,若有必要的话可以把C++函数再封装一层,在内部再做一次赋值。同时调用的时候就做好空间申请。

1
2
3
4
5
SRawStockDict[] sRawStockDict = new SRawStockDict[20000];
for (int i = 0; i < sRawStockDict.Length; i++)
{
sRawStockDict[i] = new SRawStockDict();
}
  • char[ ]可直接用byte[ ]作为匹配输入。

最后,推荐一个专门用来做P/Invoke参数及类型转换的工具。
P/Invoke Interop Assistant GUI Tool