环境:
1 P/Invoke(平台调用):
C#可以通过P/Invoke调用C++编写的DLL中的函数。
1.1 适用范围:
P/Invoke 是一种在 C# 程序中调用非托管代码(如 C++ 动态链接库)的方式。这种方法适用于函数调用相对简单的情况。
1.2 步骤:
- 在 C++ 中编写算法并编译生成 DLL。
- 在 C# 项目中使用
DllImport
属性引入 DLL。
1.3 案例程序:
1.3.1 新建一个ExportedFunctions.cpp【函数调用法】
// MathFunctions.cpp
#include "MathFunctions.h"
int Add(int a, int b) {
return a + b;
}
特点:
- 使用了
extern "C"
,这告诉编译器按照C语言的方式处理函数名,避免了名称修饰。- 使用了
__declspec(dllexport)
,这指示编译器导出该函数,使其在DLL中可见。- 这是一个自由函数(不属于任何类),可以直接被P/Invoke调用。
1.3.2 新建一个 ExportedFunctions.h
// MathFunctions.h
#pragma once
extern "C" {
__declspec(dllexport) int Add(int a, int b);
}
1.3.3 在C#中调用动态库的函数:
using System;
using System.Runtime.InteropServices;
class Program
{
// 使用DllImport属性声明DLL中的函数
[DllImport("MathFunctions.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Add(int a, int b);
static void Main(string[] args)
{
// 调用DLL中的Add函数
int result = Add(5, 10);
Console.WriteLine("The result of adding 5 and 10 is: " + result);
}
}
2 VS STUDIO 生成DLL的DllMain
模板给出的意义:
2.1 dllmain.cpp
文件
包含了
DllMain
函数的框架。DllMain
是DLL的入口点,它是一个特殊的函数,由Windows操作系统在DLL的生命周期中的关键时刻自动调用。这个函数的原型由Windows API定义,其作用是处理DLL的各种加载和卸载事件。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
DllMain
函数的参数如下:
HMODULE hModule
:被加载DLL的模块句柄。DWORD ul_reason_for_call
:调用原因代码,指示为什么调用DllMain
。LPVOID lpReserved
:保留参数,用于特定调用原因的附加信息。
ul_reason_for_call
参数可以有以下几种值:
DLL_PROCESS_ATTACH
:DLL被加载到地址空间中时调用。这是初始化DLL设置的好地方,例如全局变量的初始化。DLL_THREAD_ATTACH
:一个新线程被创建到包含DLL的进程中时调用。这允许DLL为每个线程设置特定的数据。DLL_THREAD_DETACH
:一个线程结束时调用,不再需要DLL的服务。这可以用来清理线程特定的数据。DLL_PROCESS_DETACH
:DLL从进程的地址空间卸载时调用。这是执行清理工作,如释放资源和注销全局变量的好地方。
在DllMain
函数中,每个case
对应一个调用原因。在默认情况下,Visual Studio生成的模板代码中,这些case
下没有任何操作,只是简单的break
语句。这意味着DLL在加载和卸载时不会执行任何特别的操作。如果你需要在DLL加载或卸载时执行特定的初始化或清理代码,你可以在相应的case
下添加你的代码。例如:
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 初始化代码,如分配资源
break;
case DLL_THREAD_ATTACH:
// 线程特定初始化代码
break;
case DLL_THREAD_DETACH:
// 线程退出时的清理代码
break;
case DLL_PROCESS_DETACH:
// 进程退出时的清理代码,如释放资源
break;
}
3 在VS 2019中,融合DllMain
模板和P/Invoke(平台调用)
3.1 在C#中进行集成:
将编译好的DLL文件放在你的C#项目可以访问到的位置。
我现在项目目录下,构建了一个lib目录,
然后,在C#中引入dll,
添加,编译好的C++库
问题:
1 尝试引入DLL
解决:
当您使用P/Invoke调用非托管代码(如C++ DLL)时,不需要注册DLL或将其作为COM组件。
确保
dll
文件位于C#项目的输出目录中(如bin/Debug
或bin/Release
),或者将DLL文件路径添加到系统PATH环境变量中。
2 无法找到入口:
Error: 无法在 DLL“AAMED_DLL_DEMO1.dll”中找到名为“Add”的入口点。
笔者的地址在下面:
D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.20.27508\bin\HostX64\x64
执行命令如下:
.\dumpbin.exe /EXPORTS K:\Prj_MotosCatch\Prj_Src\AAMED_LIB_Demo\AAMED_DLL_DEMO1\x64\Debug\AAMED_DLL_DEMO1.dll
结果:
PS D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.20.27508\bin\HostX64\x64> .\dumpbin.exe /EXPORTS K:\Prj_MotosCatch\Prj_Src\AAMED_LIB_Demo\AAMED_DLL_DEMO1\x64\Debug\AAMED_DLL_DEMO1.dll
Microsoft (R) COFF/PE Dumper Version 14.20.27525.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file K:\Prj_MotosCatch\Prj_Src\AAMED_LIB_Demo\AAMED_DLL_DEMO1\x64\Debug\AAMED_DLL_DEMO1.dllFile Type: DLL
Summary
1000 .00cfg
1000 .data
2000 .idata
1000 .msvcjmc
3000 .pdata
3000 .rdata
1000 .reloc
1000 .rsrc
9000 .text
10000 .textbss
确实没有看到add的函数入口
修正代码,之前想按CLASS来写不行:
然后,重新执行:
PS D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.20.27508\bin\HostX64\x64> .\dumpbin.exe /EXPORTS K:\Prj_MotosCatch\Prj_Src\AAMED_LIB_Demo\AAMED_DLL_DEMO1\x64\Debug\AAMED_DLL_DEMO1.dll
Microsoft (R) COFF/PE Dumper Version 14.20.27525.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file K:\Prj_MotosCatch\Prj_Src\AAMED_LIB_Demo\AAMED_DLL_DEMO1\x64\Debug\AAMED_DLL_DEMO1.dll
File Type: DLL
Section contains the following exports for AAMED_DLL_DEMO1.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
1 number of functions
1 number of names
ordinal hint RVA name
1 0 0001133E Add = @ILT+825(Add)
Summary
1000 .00cfg
1000 .data
2000 .idata
1000 .msvcjmc
3000 .pdata
3000 .rdata
1000 .reloc
1000 .rsrc
9000 .text
10000 .textbss
这时候,已经能看到Add的函数定义了,运行后结果正常,可以运行DLL的函数内容。