C++ 之动态链接库DLL使用注意事项及C#调用详解
- 有时候算法开发完成之后需要封装成动态链接库DLL来进行集成,一方面增加了算法or代码的复用或者广泛使用性,另一方面也起了保密的效果
- 平时封装成DLL之后放到一台新的电脑上会出现问题,所以本文总结一下其中问题及解决方式
- C#调用DLL这个和C++也有不同之处,至于详细解释,后边慢慢增加
- 关于C++ 与 C# 之间的数据对应关系,可以参考我的另一篇文章:https://blog.csdn.net/yohnyang/article/details/128355275
1. DLL封装
1.1 封装
-
当C++开发好了code后,封装可以有两种方式:
-
- VS中新建
DLL
项目,可以将开发的code复制到新项目中,编译生成DLL
。一般新建一个DLL项目后,会自动生成如下几个文件:
- VS中新建
-
cpp
//**************************************************************************************************
//framework.h
#pragma once
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
//**************************************************************************************************
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
#ifndef PCH_H
#define PCH_H
// 添加要在此处预编译的标头
#include "framework.h"
#endif //PCH_H
//**************************************************************************************************
// pch.cpp: 与预编译标头对应的源文件
#include "pch.h"
#include<iostream>
// 当使用预编译的头时,需要使用此源文件,编译才能成功。
//**************************************************************************************************
// 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;
}
将想要封装的code填充到pch
中:
cpp
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
#ifndef PCH_H
#define PCH_H
// 添加要在此处预编译的标头
#include "framework.h"
extern "C" _declspec(dllexport) int test1(int a, float* output);
#endif //PCH_H
//***********************************************************************************************
// pch.cpp: 与预编译标头对应的源文件
#include "pch.h"
#include<iostream>
// 当使用预编译的头时,需要使用此源文件,编译才能成功。
int test1(int a, float* output)
{
output[0] = a;
printf("test1 ok....");
return 1;
}
- 另一种封装方式就是直接在C++项目中包含常用头文件,然后将输出文件有
.exe
改为.dll
,其他属性酌情修改~
cpp
#inlcude<Windows.h>
1.2 查询DLL依赖
-
打开VS2019目录下自带的命令行窗口,输入命令
dumpbin /dependents xx.dll
查询即可:
-
如果调用时无法打开,可以先查询一下依赖,然后在编译电脑中找出相关的
.dll
复制到目标路径下即可,一般是在C:/
盘路径下的C:\Windows\System32
文件夹中及环境变量路径下进行 search- C++ 调用
.dll
打不开时会有提示缺少哪个 - C# 调用
.dll
打不开时只会提示无法找到.dll
- C++ 调用
2. 调用
2.1 C++ 调用
- 新建VS项目,右键项目名称添加新建项
test_0.cpp
,编写测试code:
cpp
/*
* 测试dll
*/
#include <iostream>
#include <Windows.h>
int main()
{
typedef int(*testFunc)();
HMODULE module = LoadLibrary(L".\\Dll1.dll");
if (module == NULL)
{
std::cout << "加载DemoDLL.dll动态库失败\n" << std::endl;
return -1;
}
else {
std::cout << "加载成功...\n";
}
testFunc test1 = (testFunc)GetProcAddress(module, "test1");
test1();
return 1;
}
2.2 C# 调用
- 新建VS 中C#空项目,将Dll1.dll放到
.\bin\x64\Release
目录下,然后 coding ex:
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
public class ABC
{
[DllImport(@"Dll1.dll", EntryPoint = "test1", CharSet = CharSet.Auto, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]
public static extern int test1(int a, float[] output);
}
static void Main(string[] args)
{
float[] data = new float[10];
ABC.test1(3,data);
Console.ReadLine();
}
}
}