[语言集成]C#调用C++编译的DLL

目标

使C#在.Net4.0平台、AnyCPU(x64)下能成功调用基于OpenCV、Eigen的C++代码编译成的dll

  • VisionPro8.2只支持.Net4.0平台、AnyCPU
  • IDE:VS2012
  • 操作系统:Win7 64位

操作步骤

C++部分

新建文件

新建项目 —— Visual C++ —— .Net Framework 4 —— 空项目
项目名称与代码文件名*.h、*.cpp、*.def最好一致,此处均取”defcpp”

  • 头文件 —— *.h
  • 源文件 —— *.cpp、*.def

defcpp.h

1
2
3
4
5
6
#ifndef DEFCPP_H_
#define DEFCPP_H_

int add(int a, int b);

#endif

defcpp.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include "defcpp.h"

using namespace std;

int add(int a, int b)
{
return a+b;
}

int main1()
{
cout<<add(1, 2);
return 0;
}

defcpp.def

1
2
3
LIBRARY defcpp
EXPORTS
add

导出dll

属性页 —— 配置属性 —— 常规 —— 目标文件扩展名(从.exe改为.dll)
属性页 —— 配置属性 —— 链接器 —— 输入 —— 模块定义文件(从空改为defcpp.def)
点击”重新生成解决方案”,生成dll
编译完的dll在C++项目根目录下/Debug/defcpp.dll

修改平台为64位

属性页 —— 配置管理器 —— 活动解决方案平台:x64
生成 —— 重新生成解决方案
编译完的dll在C++项目根目录下/x64/Debug/defcpp.dll

调试技巧

把*.def中的内容注释掉(每行开始加;),把defcpp.cpp中的main1()改为main(),即可成功运行

常见报错

c++编译dll时可能出现的报错:

  1. “error LNK1561: 必须定义入口点”
    解决方法:在属性页中配置 模块定义文件*.def
    使用dumpbin命令验证dll中的函数是否导出成功
    1
    dumpbin /exports a.dll

C#部分

调用dll

新建项目 —— Visual C# —— .Net Framework 4 - Windows窗体应用程序
将dll放在C#项目根目录下/bin/Debug/defcpp.dll
代码格式正确,即可成功运行

Form1.cs

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
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices; // 调用dll所必须的

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
[DllImport("defcpp.dll", EntryPoint = "add", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static int add(int a, int b);

public Form1()
{
InitializeComponent();

int res = add(1, 2); // 调用dll中的函数add()
}
}
}

常见报错

c#调用dll时可能出现的报错:

  1. “检测到LoaderLock 正尝试在OS加载程序锁内执行托管代码。…”
    解决方法:将C++代码中的main()函数改个名字,重新编译。(C++中写主函数,会导致C#调用时发生死锁)

  2. “对PInvoke 函数…的调用导致堆栈不对称”
    解决方法:修改C#代码中导入dll时的参数[DllImport(… CallingConvention …)],参考:函数调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配

  3. “试图加载格式不正确的程序。(异常来自HRESULT:0x8007000B)”
    解决方法:C++编译成的dll的平台(32位/64位),与C#选择的平台不对应

  4. 关于Any CPU
    参考:关于C#编译方式的一些说明(x86\x64\anycpu)

测试结果

C++ C# 运行结果
.Net 4.0、平台Win32 .Net 4.0、目标平台x86 成功
同上 .Net 4.0、目标平台Any CPU/x64 失败
.Net 4.0、平台x64 .Net 4.0、目标平台Any CPU/x64 成功
同上 .Net 4.0、目标平台x86 失败

小结:事实证明,c#选择.Net 4.0、.Net 4.5效果相同,关键在于32位/64位需要对应

进阶

非本机运行

目标:C#代码与dll拷贝到其他电脑时不必配置环境变量
操作:将涉及到的OpenCV的dll拷贝到与自己生成的dll同一目录下,即可成功运行
测试:改变OpenCV的dll所在目录的文件夹名称

更改OpenCV的dll名称

配置OpenCV(64位)、Eigen

OpenCV2.4.9的安装目录:

1
D:\_xmvision\opencv\

Eigen3.3.5的安装目录(修改eigen的原始目录名为eigen3):

1
D:\_xmvision\eigen3下

配置OpenCV

环境变量PATH(使编译可以找到32位/64位的dll、.dll .pdb .ilk)

1
D:\_xmvision\opencv\build\x64\vc11\bin

VC++目录->包含目录(C++程序可以include opencv、.h .hpp)

1
2
3
D:\_xmvision\opencv\build\include
D:\_xmvision\opencv\build\include\opencv
D:\_xmvision\opencv\build\include\opencv2

VC++目录->库目录(静态链接库、.lib .exp)

1
D:\_xmvision\opencv\build\x64\vc11\lib

链接器->输入->附加依赖项(动态链接库的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
opencv_calib3d249d.lib
opencv_contrib249d.lib
opencv_core249d.lib
opencv_features2d249d.lib
opencv_flann249d.lib
opencv_gpu249d.lib
opencv_highgui249d.lib
opencv_imgproc249d.lib
opencv_legacy249d.lib
opencv_ml249d.lib
opencv_nonfree249d.lib
opencv_objdetect249d.lib
opencv_ocl249d.lib
opencv_photo249d.lib
opencv_stitching249d.lib
opencv_superres249d.lib
opencv_ts249d.lib
opencv_video249d.lib
opencv_videostab249d.lib

用python获取”附加依赖项”

在ipython中输入如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 切换目录
cd D:\_xmvision\opencv\build\x64\vc11\lib

# 读取文件
import os
file_list = os.listdir()

# 过滤出"d.lib"结尾的文件名
file_lib_list = [x for x in file_list if x[-5:]=="d.lib"] # debug使用的lib

# 输出文件名
for ele in file_lib_list:
print(ele)

配置Eigen

VC++目录->包含目录

1
D:\_xmvision\eigen3

参考

编译dll

配置环境

修改dll名称

常见报错:配置完成后,编译成功,运行程序时出现:”无法启动此程序 因为计算机中丢失opencv_core243d.dll”
解决方法:重启计算机即可