wpf调用C++视觉算法
场景:C++ 编译 非托管 DLL(OpenCV、图像识别、检测算法),C#(WPF / 控制台)调用。
两种主流方式:
P/Invoke(DllImport):简单函数导出,适合轻量接口。Platform Invoke,中文一般叫「平台调用」。
C++/CLI 桥接层:适合传递复杂结构体、图像数据(OpenCV Mat)【视觉项目首选】
⚠️ 重点坑:
报无法加载DLL
平台不一致:C++ 编译 x64,C# 项目属性 → 生成 → 平台目标 x64,禁止 AnyCPU
缺少依赖:OpenCV dll、vcredist 运行库,放到 exe 同目录
区分 Debug/Release:不要 Debug C++dll 给 Release C# 调用
内存崩溃、乱码、参数错乱
C++ 导出函数必须 extern "C"
CallingConvention 统一:Cdecl(绝大多数)不要混用 StdCall
| 项目 | Cdecl (__cdecl) | StdCall (__stdcall) |
|---|---|---|
| 栈清理者 | 调用方(C# CLR) | 被调用方(C++ 函数) |
| 可变参数 | ✅ 支持 | ❌ 不支持 |
| C# 枚举 | CallingConvention.Cdecl |
CallingConvention.StdCall |
| 典型场景 | 自己编写的 C/C++ 算法 DLL、OpenCV 相关 | Windows 原生 Win32 API |
| 名称修饰 (MSVC) | _Func |
_Func@N |
字符串:CharSet.Ansi,路径中文尽量用 UTF8 改进
数据缓冲区溢出
C++ 不能直接 new 内存交给 C# 释放;
原则:谁分配,谁释放。
如果 DLL 内部分配内存,必须提供释放接口,否则内存泄漏。
方式一
// Algorithm.cpp
#include "Algorithm.h"
#include <opencv2/opencv.hpp>
/*关键:extern "C" 防止 C++ 名字修饰,DllImport 才能找到函数*/
extern "C" ALG_API int DetectImage(const char* imgPath, float* outBox, int* boxCount)
{
cv::Mat mat = cv::imread(imgPath);
if (mat.empty()) return -1;
std::vector<float> boxes;
// =====视觉检测逻辑=====
// ...填充boxes
*boxCount = boxes.size();
memcpy(outBox, boxes.data(), boxes.size() * sizeof(float));
return 0;
}
C#调用
using System;
using System.Runtime.InteropServices;
public static class AlgorithmWrapper
{
/// <summary>
/// 图像检测,[DllImport] 特性 = 声明使用 P/Invoke
/// </summary>
/// <param name="imgPath">图片路径</param>
/// <param name="outBox">输出检测框数组 [x1,y1,x2,y2,conf,cls...]</param>
/// <param name="boxCount">输出框数量</param>
[DllImport("Algorithm.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int DetectImage(string imgPath, float[] outBox, ref int boxCount);
/*
CallingConvention = 栈的协商规则;
自己写的 C++ 算法 DLL(extern "C")一律用 Cdecl; C# 和 C++ 两边必须保持一致; 不要省略该参数,.NET 默认不是 Cdecl,隐藏大坑
CharSet.Ansi控制 字符串如何在 C# 托管字符串 ↔ C++ char* 之间转换封送。CharSet 只影响 string 参数。
ANSI:C++ char(单字节字符);Unicode:C++ wchar_t(宽字符)
*/
}
// 使用示例
public void RunDetect(string imagePath)
{
int boxCnt = 0;
// 先调用一次获取数量(也可以预分配足够大缓冲区)
int ret = AlgorithmWrapper.DetectImage(imagePath, null, ref boxCnt);
float[] resultBoxes = new float[boxCnt];
ret = AlgorithmWrapper.DetectImage(imagePath, resultBoxes, ref boxCnt);
if (ret == 0)
{
Console.WriteLine($"检测到目标数量:{boxCnt}");
}
}
视觉最大痛点:直接传递OpencvMat
原因:extern "C" 只能导出普通 C 风格函数,不能导出 C++ 类。cv::Mat 是 C++ OpenCV 的内部类,不是标准 C 类型,C# 完全不认识它的内存结构,P/Invoke 无法跨语言直接传递。
P/Invoke 不能直接传 cv::Mat(非基础类型,内存布局不兼容)两种解决方案:
方案B:C++/CLI中间桥接
C# (WPF) ←→ C++/CLI 桥接层 ←→ 原生C++算法DLL
// C++/CLI 桥接代码
#include "NativeAlgorithm.h"
public ref class VisionDetector
{
private:
NativeDetector* _detector;
public:
VisionDetector()
{
_detector = new NativeDetector();
}
~VisionDetector()
{
delete _detector;
}
int Detect(System::String^ imgPath)
{
std::string path = marshal_as<std::string>(imgPath);
return _detector->Detect(path);
}
};
c#
var detector = new VisionDetector();
int code = detector.Detect(@"d:\test.jpg");
方案A:传图像像素数据(byte数组)优点:简单;缺点:内存拷贝开销,大图实时视频流性能一般
C# → byte [] BGR 像素 → 传入 DLL,C++ 还原 Mat
/// <summary>
/// BitmapSource 转为 BGR byte数组(3通道,无Alpha)
/// </summary>
public static byte[] BitmapSourceToBgrBytes(BitmapSource source)
{
int width = source.PixelWidth;
int height = source.PixelHeight;
int stride = width * 3; // BGR 3通道
// 创建格式转换器:强制转为 Bgr24
var format = PixelFormats.Bgr24;
if (source.Format != format)
{
source = new FormatConvertedBitmap(source, format, null, 0);
}
byte[] bgrData = new byte[height * stride];
source.CopyPixels(Int32Rect.Empty, bgrData, stride, 0);
return bgrData;
}
| 类型 | 通道 | 俗称 |
|---|---|---|
| CV_8UC1 | 单通道 | Mono8 灰度图(大华黑白相机) |
| CV_8UC3 | 三通道 | BGR 彩色图 |
| CV_8UC4 | 四通道 | BGRA 彩色 + 透明通道 |
| CV_8UC2 | 双通道 | 几乎不用来存图像,多用于光流 (ux,uy) |
cpp
extern "C" ALG_API int DetectByPixel(unsigned char* data, int width, int height, int channel, float* outBox, int* boxCount);
C#
[DllImport("Algorithm.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DetectByPixel(byte[] pixelData, int width, int height, int channel, float[] outBox, ref int boxCount);
浙公网安备 33010602011771号