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);
posted @ 2026-07-23 00:31  HelloWorld庄先生  阅读(2)  评论(0)    收藏  举报