CSharp 调用动态链接库 DLL
本文总结如何在 CSharp 调用动态链接库
托管代码调用非托管代码的 2 种方式方式?
- 利用 P/Invoke (平台调用) 实现直接调用: 使用该方法在托管代码内引入非托管 DLL,但是问题是 PInvoke 不能简单的实现 C++ 类的调用
- 利用 C++/CLI 作为代理中间层:直接使用 C++/CLI 编写托管 DLL,可以实现 C# 调用 C++ 所写的类,但是问题是 MONO 架构不支持 C++/CIL 功能,因此无法实现脱离 Microsoft.NET Framework 跨平台运行
什么是 P/Invoke (平台调用)?
- P/Invoke 的全称是 Platform Invoke (平台调用) 它实际上是一种函数调用机制,通过 P/Invoke 允许从托管代码访问非托管库中的结构、回调和函数的技术。大多数 P/Invoke API 包含在两个命名空间中:System 和 System.Runtime.InteropServices
- PInvoke 最简单,但只能调用函数,不能直接调用类, 但有一个折衷的办法,就是在 C++ 里面定义一系列函数,里面调用相应的类,暴露给调用方(托管语言)的只有一系列的函数接口(API)
1
2
3
4extern "C"
{
CPLUSPLUSTEST_API int adjustBrightnessContrast(unsigned char* src, int w, int h, int channel);
}
托管代码通过 DllImport 导入 C++ 的 DLL 的过程?
- C# 通过 P/Invoke (平台调用) 使用 C++ 的 DLL 时,需要使用 DllImport 导入
- DllImport:用来标识该方法是非托管的代码方法,在编译器编译的时候它能够正确的认识出被该 特性标记的是外来代码段。当到达程序运行的时候,也能够正确的认识出该代码是引用非托管的 代码,这样 CLR 会去加载非托管 DLL 文件,然后查找到入口点进行调用
1
2
3
4
5// CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;
// SetLastError 指示方法是否保留 Win32"上一错误",如:SetLastError=true;
// ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=false;
// CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Winapi;
[ ]
托管代码通过 P/Invoke (平台调用) 使用 C++ 函数的过程?
- (1) 定义预定于宏:用于声明 dll 中开放的函数
1
2
3
4
5
- (2) 确定调用约定:参考 DLL 函数的调用约定?
1
2
3
4
5
6
7
8
9
10
11//1.1 标准调用约定
EXPORTDLL_API void _stdcall CallingCvt_Stdcall()
{
wprintf(L"CallingCvt_Stdcall\n");
}
//1.2 C调用约定
EXPORTDLL_API void _cdecl CallingCvt_Cdecl()
{
wprintf(L"CallingCvt_Cdecl\n");
} - (3) C# 导入:使用 DllImport 导入 dll
1
2
3
4
5
6
7//1.1 标准调用约定
[ ]
public static extern void CallingCvt_Stdcall();
//1.2 C调用约定
[ ]
public static extern void CallingCvt_Cdecl(); - (4) 测试:
1
2
3
4
5//1.1 标准调用约定
CExportDll.CallingCvt_Stdcall();
//1.2 C调用约定
CExportDll.CallingCvt_Cdecl();
什么是数据封送?
- 托管代码使用 P/Invoke (平台调用) 使用非托管代代码函数时,设计参数的传递,但是由于他们分别基于公共语言运行时 (CLR) 、本机代码(Native Code),两者数据存储机制不一样,需要在托管代码一方进行数据封送处理。 封送指的就是托管内存和非托管内存之间传递数据的过程
- 封送是双向的,由封送拆收器完成,其主要任务是: 1. 数据类型转换。非托管数据类型到托管数据类型的相互转换(输出),或者,托管数据类型到非托管数据类型的转换(输入); 2. 内存搬运。非托管内存复制到托管内存,或者,托管内存复制到非托管内存; 3. 内存释放
- 对于每个 .NET Framework 类型均有一个默认非托管类型,可以使用 MarshalAs 重写默认封送处理: string 类型默认非托管类型是 LPTSTR,可以在非托管函数的 C# 声明中使用 MarshalAs 属性重写默认封送处理
1
2
3// puts 函数的参数的默认封送处理已从默认值 LPTSTR 重写为 LPSTR
[ ]
public static extern int puts([MarshalAs(UnmanagedType.LPStr)] string m);
托管代码和非托管代码的数据传递为什么要进行封送?
- 主要是数据存储顺序不一致
- 在 C/C++ 中,struct 类型中的成员的一旦声明,则实例中成员在 内存中的布局 (Layout) 顺序就定下来了
- 在.net 托管环境中, CLR 提供了更自由的方式来控制 struct 中 Layout:我们可以在定义 struct 时,在 struct 上运用 StructLayoutAttribute 特性来控制成员的内存布局。默认情况下,struct 实例中 的字段在栈上的布局 (Layout) 顺序与声明中的顺序相同,即在 struct 上运用 StructLayoutAttribute (LayoutKind.Sequential)] 特性
通过 P/Invoke 如何封送 “基础数据类型”?
- 对于 P/Invoke (平台调用),具有相同格式的托管和 c 本机基元类型之间不需要封送处理,但是必须对不具有相同形式的类型进行封送处理。例如在 C/C 中 long 和 int 都是 4 个字节,都对应着 C# 中的 int 类型,而 C/C++ 中的 char 类型占一个字节,用来表示一个 ASCII 码字符,在 C# 中能够表示一个字节的是 byte 类型
- C++ 定义接口
1
void func1(int a, double b, char c,int& d, double& e, char& f)
- C# 调用
1
2
3
4
5
6
7
8
9
10
11[ ]
public static extern unsafe void func1(int a, double b, char c, out int d,out double e, out char f);
public static void test_func1()
{
int a = 1; double b = 2.2;char c = 'H';
int d=0; double e=0; char f=' ';
Console.WriteLine("[i n]C# :"+a + "-" + b + "-" + c + "-" + d + "-" + e + "-" + f);
func1(a, b, c, out d, out e, out f);
Console.WriteLine("[out]C# :" + a + "-" + b + "-" + c + "-" + d + "-" + e + "-" + f);
}
通过 P/Invoke 方式如何封送数组 / 指针?
- 1)- C++ 定义为指针,C# 使用 fxied 将数组转为指针封送;2)如果 C# 需要拿到 C ++ 的不定长的数组返回,需要使用 Marshal 解析封装;3)C# 可使用指针向 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
26
27
28
29
30void func2(int* a, int aNum,int* b,int*& c, int& cNum, int* d, int dNum) {
// 打印C#传递过来的数组
std::cout << "[in]C++/a:";
for (int i = 0; i < aNum; i++)
{
std::cout << a[i] << "-";
}
std::cout << "" << endl;
b[0] = 10;
b[1] = 12;
std::cout << "[out]C++/b:" << b[0] << "-" << b[1] << endl;
cNum = 10;
c = new int[cNum];
std::cout << "[out]C++/c:";
for (int i = 0; i < cNum; i++)
{
c[i] = 100 + i;
std::cout << c[i] << "-";
}
std::cout << "" << endl;
std::cout << "[in]C++/d:";
for (int i = 0; i < dNum; i++) {
std::cout << d[i] << "-";
d[i] = i * 10 + 2;
}
std::cout << "" << endl;
}; - 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45[ ]
public static extern unsafe void func2(int* a, int aNum,int* b,out IntPtr c, out int cNum,int[] d,int dNum);
public static void test_func2()
{
int aNum = 3;
int[] a=new int[aNum];
for(int i=0;i<aNum;i++){a[i] = i + 10;}
int[] b = new int[2];
int dNum = 3;
int[] d = { 1, 2, 3 };
int cNum = 0;
unsafe
{
fixed (int* a_ptr = a)
{
fixed (int* b_ptr = b)
{
IntPtr c;
func2(a_ptr, aNum, b_ptr, out c, out cNum,d,dNum);
Console.WriteLine("[out]C#/b:" + b[0]+","+b[1]);
int[] arr = new int[cNum];
Marshal.Copy(c, arr, 0, cNum);
for (int i = 0; i < cNum; i++)
{
Console.Write(arr[i] + "-");
}
Console.WriteLine("");
for (int i = 0; i < dNum; i++)
{
Console.Write(d[i] + "-");
}
}
}
}
}
//1. 数组a:C#向C++传递数组,并使用aNum确定数量
//2. 数组b:C#申请空间,C++按约定空间赋值,然后C#打印
//3. 数组c:C#向C++传递IntPtr指针,C++动态返回不定长的数据,然后C#使用Marshal解析数据
//4. 数组d:类似数组b,C#声明不一样,使得不用使用fixed
通过 P/Invoke 方式如何封送结构体?
- C# 和 C++ 定义相同的数据结构,然后将数据结构当作基础数据类型使用
- 需要接收 C++ 返回时,使用 out 修饰符
- C++ 定义函数
1
2
3
4
5
6
7void func3(point a, point& b) {
std::cout << "[in]C++/a: " << a.x << "," << a.y << endl;
b.x = 10;
b.y = 100;
std::cout << "[out]C++/b: " << b.x << "," << b.y << endl;
} - C# 调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14[ ]
public static extern unsafe void func3(point a,out point b);
public static void test_func3()
{
point a=new point(1,2);
Console.WriteLine("[in]C#/a:" + a.x + "," + a.y);
point b;
func3(a, out b);
Console.WriteLine("[out]C#/b:" + b.x + "," + b.y);
}
//1. 结构a:C#向C++传递结构数据
//2. 结构b:C++声明结构变量,C++赋值,C#可以拿到其值
通过 P/Invoke 方式如何封送结构数组(单层指针)?
- 结构数组和基础数据类型的数组传递方法类似
- C++ 定义函数
1
2
3
4
5
6
7
8
9
10
11
12
13void func4(point* a, int aNum, point* b, int bNum) {
for (int i = 0; i < aNum; i++)
{
std::cout << a[i].x << "," << a[i].y << endl;
}
for (int i = 0; i < bNum; i++)
{
b[i].x = (i+1)*10+i;
b[i].y = (i + 2) * 10 + i;
}
} - 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
26
27
28
29
30
31
32
33
34[ ]
public static extern unsafe void func4(point* a, int aNum,point* b,int bNum);
public static void test_func4()
{
point[] a =
{
new point(1,2),
new point(3,4),
new point(5,6)
};
int aNum = a.Length;
int bNum = 2;
point[] b = new point[bNum];
unsafe
{
fixed(point* a_ptr=a)
{
fixed (point* b_ptr = b)
{
func4(a_ptr, aNum, b_ptr, bNum);
for (int i = 0; i < bNum; i++)
{
Console.WriteLine(b[i].x + "," + b[i].y);
}
}
}
}
}
// 1. 结构指针a:既可向C++传递数据,也可向C#传递数据
// 2. 结构指针b:既可向C++传递数据,也可向C#传递数据
通过 P/Invoke 方式如何封送结构数组(嵌套指针)?
- 多重指针,最外层需要在 C# 指定空间,或者使用 IntPtr 传递
- 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
25void func6(point** ployPoints, int ployNum, int* ployPointsNum)
{
ployPoints[0] = new point[1];
ployPoints[0][0].x = 1.1;
ployPoints[0][0].y = 2.1;
ployPoints[1] = new point[2];
ployPoints[1][0].x = 3.1;
ployPoints[1][0].y = 3.2;
ployPoints[1][1].x = 4.1;
ployPoints[1][1].y = 4.2;
ployPoints[2] = new point[3];
ployPoints[2][0].x = 5.1;
ployPoints[2][0].y = 5.2;
ployPoints[2][1].x = 6.1;
ployPoints[2][1].y = 6.2;
ployPoints[2][2].x = 7.1;
ployPoints[2][2].y = 7.2;
ployNum = 3;
ployPointsNum[0] = 1;
ployPointsNum[1] = 2;
ployPointsNum[2] = 3;
} - 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
26
27[ ]
public static extern unsafe void func6(point** ployPoints, int ployNum, int[] ployPointsNum);
// C#申请第一层空间,C++申请第二层空间
public static void Test_func6()
{
unsafe
{
int ployNum = 3;
point*[] ployPointsList = new point*[ployNum];
int[] ployPointsNum = new int[ployNum];
fixed (point** ployPoints = ployPointsList)
{
func6(ployPoints, ployNum, ployPointsNum);
Console.WriteLine(ployNum);
for (int i = 0; i < ployNum; i++)
{
for (int j = 0; j < ployPointsNum[i]; j++)
{
Console.WriteLine(ployPointsList[i][j].x + "," + ployPointsList[i][j].y);
}
}
}
}
}
// 1. 结构指针ployPoints:双重指针,先在C#申请第一重空间,然后在C++申请第二重空间,并返回每个二重空间的大小
// 2. 指针指向空间长度指针ployPointsNum:第二层空间的大小,是一个列表
通过 P/Invoke 方式 C# 如何接受 C++ 的” 结构体 " 函数返回?
- C++ 定义开放函数
1
2
3
4
5
6
7
8
9
10
11
12typedef struct _testStru5
{
int iVal;
}testStru5;
testStru5 g_stru5;
EXPORTDLL_API testStru5* Struct_Return()
{
g_stru5.iVal = 5;
wprintf(L"Struct_Return \n");
return(&g_stru5);
} - C# 接受数据
1
2
3
4
5
6
7
8
9
10[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct testStru5
{
public int iVal;
};
[DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr Struct_Return();
IntPtr struIntPtr = Struct_Return();
testStru5 stru5 = (testStru5)(Marshal.PtrToStructure(struIntPtr, typeof(testStru5)));
通过 P/Invoke 方式 C# 如何互送 C++ 的” 结构体数组 (内含基础类型一维数组)“?
- C++ 定义开放函数
1
2
3
4
5
6
7
8
9
10
11
12struct cmppe_submit
{
char user[10];
};
extern "C" __declspec(dllexport) void GetUser(cmppe_submit* lpSubit)
{
pData->data[0] = 1;
pData->data[1] = 2;
pData->data[2] = 3;
} - C# 接受数据
1
2
3
4
5
6
7
8
9
10
11
12struct MyData
{
[ ]
public int[] data;
};
[ ]
private extern static void GetData(ref MyData pData);//用ref声明结构
MyData sd = new MyData();
sd.data = new int[10];
GetData(ref sd);
通过 P/Invoke 方式 C# 如何互送 C++ 的” 结构体数组 (内含基础类型二维数组)“?
- C++ 定义开放函数
1
2
3
4
5
6
7
8
9struct cmppe_submit
{
char user[10][200];
};
extern "C" __declspec(dllexport) void GetUser(cmppe_submit* lpSubit)
{
strcpy(lpSubit->user[0], "waqerqwdsewf");
} - 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
26
27
28
29
30
31
32
33
34// 使用一维数组转写对应结构
struct cmppe_submit
{
[ ]
public byte[] dst_addr;
}
[ ]
private extern static void GetUser(ref cmppe_submit lpSubmit);//用ref声明结构
cmppe_submit submit;
submit.dst_addr = new byte[2000];
GetUser(ref submit);
string str = System.Text.Encoding.Default.GetString(submit.dst_addr, 0, 25);
// 使用2个1维数组转写对应结构
[ ]
public struct cmppe_submit_pre
{
[ ]
public int []m;
};
[ ]
public struct cmppe_submit_now
{
[ ]
public cmppe_submit_pre[]m;
};
[ ]
private extern static void GetUser(ref cmppe_submit_now lpSubmit);//用ref声明结构
cmppe_submit_now submit=new cmppe_submit_now();
GetUser(ref submit);
通过 P/Invoke 方式 C# 如何互送 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
25typedef struct _testStru9
{
WCHAR *pWChArr;
CHAR *pChArr;
bool IsCbool;
BOOL IsBOOL;
}testStru9;
EXPORTDLL_API void Struct_ChangePtr( testStru9 *pStru )
{
if (NULL == pStru)
{
return;
}
pStru->IsBOOL = true;
pStru->IsBOOL = TRUE;
pStru->pWChArr = (WCHAR*)CoTaskMemAlloc(8*sizeof(WCHAR));
pStru->pChArr = (CHAR*)CoTaskMemAlloc(8*sizeof(CHAR));
lstrcpynW(pStru->pWChArr, L"ghj", 8);
lstrcpynA(pStru->pChArr, "ghj", 8);
wprintf(L"Struct_ChangePtr \n");
} - C# 接受数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[ ]
public struct testStru9
{
[ ]
public string pWChArr;
[ ]
public string pChArr;
[ ]
public bool IsCbool;
[ ]
public bool IsBOOL;
};
[ ]
public static extern void Struct_ChangePtr(ref testStru9 pStru);
testStru9 stru9 = new testStru9();
Struct_ChangePtr(ref stru9);
通过 P/Invoke 方式 C# 如何互送 C++ 的” 结构体二维指针 "?
- C++ 定义开放函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _testStru8
{
int m;
}testStru8;
EXPORTDLL_API void Struct_ParameterOut( testStru8 **ppStru )
{
if (NULL == ppStru){
return;
}
*ppStru = (testStru8*)CoTaskMemAlloc(sizeof(testStru8));
(*ppStru)->m = 8;
wprintf(L"Struct_ParameterOut \n");
} - C# 接受数据
1
2
3
4
5
6
7
8
9
10
11
12[ ]
public struct testStru8
{
public int m;
};
[ ]
public static extern void Struct_ParameterOut(ref IntPtr ppStru);
IntPtr outPtr = IntPtr.Zero;
Struct_ParameterOut(ref outPtr);
testStru8 stru8 = (testStru8)(Marshal.PtrToStructure(outPtr, typeof(testStru8)));
Marshal.FreeCoTaskMem(outPtr); // 释放非托管的内存
通过 P/Invoke 如何封送” 嵌套的结构体 “?
- 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
26
27
28
29typedef struct _testStru10Pre
{
int iVal;
}testStru10Pre;
typedef struct _testStru10
{
testStru10Pre *pPre;
long lVal;
_testStru10()
{
pPre = NULL;
}
}testStru10;
EXPORTDLL_API void Struct_NestStruct( testStru10 *pStru )
{
if (NULL == pStru)
{
return;
}
pStru->lVal = 10;
if (NULL != pStru->pPre)
{
pStru->pPre->iVal = 9;
}
wprintf(L"Struct_NestStruct \n");
} - C# 接受数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public struct testStru10Pre
{
public int iVal;
};
public struct testStru10
{
public IntPtr pPre;
public int lVal;
};
[DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
public static extern void Struct_NestStruct(ref testStru10 pStru);
testStru10Pre str10Pre = new testStru10Pre();
IntPtr intPtrStru10Pre = Marshal.AllocCoTaskMem(Marshal.SizeOf(str10Pre));
Marshal.StructureToPtr(str10Pre, intPtrStru10Pre, false);
testStru10 stru10 = new testStru10();
stru10.pPre = intPtrStru10Pre;
Struct_NestStruct(ref stru10);
testStru10Pre str10Pre2 = (testStru10Pre)Marshal.PtrToStructure(stru10.pPre, typeof(testStru10Pre));
Marshal.DestroyStructure(intPtrStru10Pre, typeof(testStru10Pre));
通过 P/Invoke 方式 C# 如何封送 “字符串” 到 C++?
- C++ 定义开放函数
1
2
3
4
5
6
7
8
9
10
11EXPORTDLL_API int Str_Output( WCHAR *pStr )
{
if (NULL == pStr)
{
return(-1);
}
wprintf(L"Str_Output %s\n", pStr);
return(0);
} - C# 接受数据
1
2
3
4
5
6[ ]
public static extern int Str_Output([MarshalAs(UnmanagedType.LPWStr)]string pStr);
string str = "hjkl;";
CExportDll.Str_Output(str);
通过 P/Invoke 方式 C# 如何接受 C++“字符串” 作为参数返回?
- C++ 定义开放函数
1
2
3
4
5
6
7
8EXPORTDLL_API int Str_Change( WCHAR *pStr, int len )
{
if (NULL == pStr)
{
return(-1);
}
for (int ix=0; ix - C# 接受数据
1
2
3
4
5
6
7
8
9[DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
public static extern int Str_ChangeArr([In, Out]string[] ppStr, int len);
string[] strArr = new string[4] {new string('\0', 10),
new string('\0', 10),
new string('\0', 10),
new string('\0', 10) };
CExportDll.Str_ChangeArr(strArr, 4);
通过 P/Invoke 如何封送” 函数指针 “?
- C# 中并没有函数指针的概念, 想要传递函数指针,首先要在 C# 中定义一个委托 (delegate),并且在 C++ 中定义一个函数指针,同时要保证 委托和函数指针具备相同的函数原型
- (1) C++ 返回函数指针
1
2
3
4
5
6
7
8
9
10
11
12
13// 定义函数指针
typedef int (*PCALLBACKFUN)(int i);
// 定义返回函数指针的函数
int PrintInt( int i )
{
return(wprintf(L"CallBack_GetFunPtr %d\n", i));
}
EXPORTDLL_API PCALLBACKFUN CallBack_GetFunPtr()
{
return(PrintInt);
} - (2) C# 定义对应 C++ 函数指针的委托
1
public delegate int DelegateGetFunPtrType(int i);
- (3) C# 导入 C++ 的函数
1
2
3[ ]
[ ]
public static extern DelegateGetFunPtrType CallBack_GetFunPtr(); - (4) 测试
1
2CExportDll.DelegateGetFunPtrType printInt = CExportDll.CallBack_GetFunPtr();
printInt(100);
C# 读取图片如何传递给 C++ 的 DLL?
- 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
25int CPLUSPLUSTEST_API opencvtest(unsigned char* src, int w, int h, int channel, float contrastValue, int brightValue)
{
if (!src)
{
return 0;
}
int format = CV_8UC3;
switch (channel)
{
case 1:
format = CV_8UC1;
break;
case 2:
format = CV_8UC2;
break;
case 3:
format = CV_8UC3;
break;
default:
return 0;
break;
}
Mat img(h, w, format, src);
} - C# 读图
1
2Bitmap bmp;
bmp = new Bitmap("1.bmp"); - C# 传递图片数据到 DLL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24int stride;
byte[] source = GetBGRValues(bmp, out stride);
int channel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
// 在原来的基础上,对比度和亮度加多少
opencvtest(source, bmp.Width, bmp.Height, channel, (float)(1), 0); //C++ opencv对内存操作
public static byte[] GetBGRValues(Bitmap bmp, out int stride)
{
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
stride = bmpData.Stride;
//int channel = bmpData.Stride / bmp.Width;
var rowBytes = bmpData.Width * Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
var imgBytes = bmp.Height * rowBytes;
byte[] rgbValues = new byte[imgBytes];
IntPtr ptr = bmpData.Scan0;
for (var i = 0; i < bmp.Height; i++)
{
Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes); //对齐
ptr += bmpData.Stride; // next row
}
bmp.UnlockBits(bmpData);
return rgbValues;
}
C# 调用托管的 DLL 时,出现 “引用无效或不支持该引用” 的问题?
- 原因:编译得到的 DLL 是非托管的,不能使用引用导入
C# 静态调用 DLL 中非托管函数时,出现 “System.EntryPointNotFoundException” ?
- 原因: DllImport 的 EntryPoint 属性和 DLL 的函数不一致
- 方法一:在 VS2019 的终端上,使用命令 dumpbin /exports convertPython.dll 查看 DLL 对外开放的函数名,然后将查到的名字填到 EntryPoint 上
- 方法二:在生成 DLL 时,按以下方式编写,设定函数对外开放接口命名格式,其他格式看 DLL 函数的调用约定?
1
2
3
4
5extern "C"
{
_declspec(dllexport) char _great_function(const char* a, int b);
_declspec(dllexport) void _startLedCheck(byte* ImageBuffer, int imageWedth, int imageHeight);
}
什么是 Marshal 类?
- 提供了一个方法集合,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法
Marshal 类的 AllocHGlobal () 方法?
- 从进程的非托管内存中分配内存
1
2
3
4
5
// Demonstrate how to call GlobalAlloc and
// GlobalFree using the Marshal class.
IntPtr hglobal = Marshal.AllocHGlobal(100);
Marshal.FreeHGlobal(hglobal);
Marshal 类的 Copy () 方法?
- 将数据从托管内存复制到非托管内存指针
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
26
27
28
29
30
31
32using System;
using System.Runtime.InteropServices;
class Example
{
static void Main()
{
// Create a managed array.
byte[] managedArray = { 1, 2, 3, 4 };
// Initialize unmanaged memory to hold the array.
int size = Marshal.SizeOf(managedArray[0]) * managedArray.Length;
IntPtr pnt = Marshal.AllocHGlobal(size);
try
{
// Copy the array to unmanaged memory.
Marshal.Copy(managedArray, 0, pnt, managedArray.Length);
// Copy the unmanaged array back to another managed array.
byte[] managedArray2 = new byte[managedArray.Length];
Marshal.Copy(pnt, managedArray2, 0, managedArray.Length);
Console.WriteLine("The array was copied to unmanaged memory and back.");
}
finally
{
// Free the unmanaged memory.
Marshal.FreeHGlobal(pnt);
}
}
}
Marshal 类的 FreeHGlobal () 方法?
- 释放以前从进程的非托管内存中分配的内存
1
2
3
4
5
// Demonstrate how to call GlobalAlloc and
// GlobalFree using the Marshal class.
IntPtr hglobal = Marshal.AllocHGlobal(100);
Marshal.FreeHGlobal(hglobal);
Marshal 类的 PtrToStringAnsi () 方法?
- 将非托管 ANSI 或 UTF-8 字符串中第一个空字符之前的所有字符复制到托管 String,并将每个字符扩展为 UTF-16 字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using namespace System;
using namespace System::Runtime::InteropServices;
void main()
{
// Create an unmanaged c string.
const char * myString = "Hello managed world (from the unmanaged world)!";
// Convert the c string to a managed String.
String ^ myManagedString = Marshal::PtrToStringAnsi((IntPtr) (char *) myString);
// Display the string to the console.
Console::WriteLine(myManagedString);
}
Marshal 类的 PtrToStructure () 方法?
- 将数据从非托管内存块封送到托管对象
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Runtime.InteropServices;
public struct Point
{
public int x;
public int y;
}
class Example
{
static void Main()
{
// Create a point struct.
Point p;
p.x = 1;
p.y = 1;
Console.WriteLine("The value of first point is " + p.x + " and " + p.y + ".");
// Initialize unmanged memory to hold the struct.
IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(p));
try
{
// Copy the struct to unmanaged memory.
Marshal.StructureToPtr(p, pnt, false);
// Create another point.
Point anotherP;
// Set this Point to the value of the
// Point in unmanaged memory.
anotherP = (Point)Marshal.PtrToStructure(pnt, typeof(Point));
Console.WriteLine("The value of new point is " + anotherP.x + " and " + anotherP.y + ".");
}
finally
{
// Free the unmanaged memory.
Marshal.FreeHGlobal(pnt);
}
}
}
Marshal 类的 SizeOf () 方法?
- 返回对象的非托管大小(以字节为单位)
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Runtime.InteropServices;
public struct Point
{
public int x;
public int y;
}
class Example
{
static void Main()
{
// Create a point struct.
Point p;
p.x = 1;
p.y = 1;
Console.WriteLine("The value of first point is " + p.x + " and " + p.y + ".");
// Initialize unmanged memory to hold the struct.
IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(p));
try
{
// Copy the struct to unmanaged memory.
Marshal.StructureToPtr(p, pnt, false);
// Create another point.
Point anotherP;
// Set this Point to the value of the
// Point in unmanaged memory.
anotherP = (Point)Marshal.PtrToStructure(pnt, typeof(Point));
Console.WriteLine("The value of new point is " + anotherP.x + " and " + anotherP.y + ".");
}
finally
{
// Free the unmanaged memory.
Marshal.FreeHGlobal(pnt);
}
}
}
Marshal 类的 StringToHGlobalUni () 方法?
- 将托管 String 的内容复制到非托管内存
Marshal 类如何将 void * 指针转为 IntPtr?
- 规定 void 类型的指针可以指向任何数据类型,只不过在 c 函数实体内强制为你的数据类型即可,比如
1
2// 函数:int fnAdd(struct mybuf *p_mydata),或者写成int fnAdd(void *p_mydata)
struct mybuf*p = (structmybuf*)p_mydata; - C++:接口声明
1
2
3
4
5
6
7struct A
{
wchar_t osdbuffer[100];
unsigned short ix;
unsigned short iy;
};
int SetConfig(int type, void* p); // 声明接口 - C#:声明调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14[ ]
public struct A {
[ ]
public string osdbuffer;
public ushort ix; //显示坐标x
public ushort iy; //显示坐标y
}
A s_a = new A();
int lenght = Marshal.SizeOf(s_a);
IntPtr pA= Marshal.AllocHGlobal(lenght);
Marshal.StructureToPtr(s_a, pA, true);
int type = 1;
int ret = SetConfig( type, pA);
Marshal.FreeHGlobal(pA);
Marshal 类如何将 byte [] 转 IntPtr?
- 不使用 Marshal 创建新的堆,节省内存开销,也避免忘记释放导致的问题
1
System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(array, 0);
Marshal 类如何将 IntPtr 转 byte []?
- Copy :使用 Marshal 对数据进行拷贝,该操作速度较慢并且会引入新空间的开辟
1
2
3IntPtr intPtr = GetBuff();
byte[] b = new byte[length];
Marshal.Copy(intPtr, b, 0, length); - byte*:该操作不会开辟新的空间,速度极快,但必须在 unsafe 模块下使用
1
2IntPtr pRet = GetBuff();
byte* memBytePtr = (byte*)pRet.ToPointer();
通过 Marshal 方式 C# 封送 “基础数据类型 (一维数组)” 到 C++ 指针?
- C++ 端定义接口
1
CPLUSPLUSTEST_API int ParsePolygonMarkNum(int w, int h, int drawPointNum, double* drawPoints)
- C# 调用
1
2
3double[] allDrawPoint = { 326, 96, 141, 618, 536, 300, 127, 297, 491, 613 };
double* pointPtr = (double*)Marshal.UnsafeAddrOfPinnedArrayElement(allDrawPoint, 0);
int rtnRs = ParsePolygonMarkNum(w, h, nowDrawPoint.Length / 2, pointPtr)