C++ DLL 供 C# AnyCPU 调用 【 OpenCV onnxruntime】

背景

C++ 打包的DLL用到 OpenCV,用到 onnxruntime

C# 软件需要打包成Any CPU版本,即可以在 x86下使用,也可以在x64下使用

C# 前端想把 C++ dll与依赖放在单独的”Libs“文件夹中,不是"Dubug"下

难点:

在C++ 中, OpenCV 和 onnxruntime 分别有x64和x86两个版本,两个版本只能在对应环境下使用

DLL 和其依赖如果不放在"Dubug"下,不容易路径。

比如在C++ dll代码中,调用了"model.onnx"。将打包后的 DLL 和 model.onnx放在C# 项目中的 "Dubug/Libs"下。C# 调用DLL时,会在 Dubug下找model.onnx,即使在C#代码中指定DLL路径,也会报"找不到模型文件onnx 的错误。

系统环境

Windows 11

Visual Studio 2019

解决方案

  1. 先用 C++ 打包两个 DLL FungiIdentification_x64.dll 和 FungiIdentification_x86.dll
    1. 两个DLL使用不同的函数接口 start_x64() 和 start_x86()
    2. 在打包时使用 getdlldir()函数获得DLL所在文件夹路径,之后使用DLL路径构建onnx模型的绝对路径(原因:DLL C++代码中设置的相对路径,C#调用后,会变成以C# exe为基准的相对路径)
  2. C# 调用时加载两个不同版本的DLL
    1. 给start_x64() 和 start_x86()再封装一个高一层的start()接口
    2. 使用windows系统"kernel32.dll"中的SetDllDirectory()函数,根据当前系统环境设置不同的依赖文件夹

具体步骤

省略配置 OpenCV 环境和 onnxruntime 环境的步骤

C++ 打包 x64版本

关键代码:

省略处理函数

#include "nlohmann/json.hpp"

#include <fstream>
#include <iostream>
#include <ctime>
#include <sstream>
#include <windows.h>
#include <filesystem>

#include "opencv2/opencv.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/core/utils/logger.hpp"

std::string getdlldir()
{
	HMODULE hDllHandle = GetModuleHandleA("FungiIdentification_x64.dll");//是本代码片所属动态库名称
	char cDllPathBuff[MAX_PATH];
	memset(cDllPathBuff, 0, MAX_PATH);
	auto getdllpathstatus = GetModuleFileNameA((HMODULE)hDllHandle, cDllPathBuff,
		MAX_PATH);
	std::string strDllPathBuff = cDllPathBuff;
	auto nPathTruncLoc = strDllPathBuff.find_last_of('\\');
	strDllPathBuff = strDllPathBuff.substr(0, nPathTruncLoc + 1);//strDllPathBuff 为本动态库所在路径
	
	return strDllPathBuff;
}

extern "C" _declspec(dllexport) const char* start_x64(char* img_path, char* img_p)
{
	cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR); // 取消警告
	std::string dlldir = getdlldir();
	nlohmann::json results;
	
	std::string img_path_str = img_path;
	process(img_path_str, dlldir, results);  // 处理函数,没放进来

	std::string results_s = results.dump(2);  //转储成字符串 可以使用dump(4)进行缩进
	char* return_char;
	return_char = strdup(results_s.c_str());
	return return_char;
}

C++ 打包 x86版本

关键代码:

省略处理函数

#include "nlohmann/json.hpp"

#include <fstream>
#include <iostream>
#include <ctime>
#include <sstream>
#include <windows.h>
#include <filesystem>

#include "opencv2/opencv.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/core/utils/logger.hpp"

std::string getdlldir()
{
	HMODULE hDllHandle = GetModuleHandleA("FungiIdentification_x86.dll");//是本代码片所属动态库名称
	char cDllPathBuff[MAX_PATH];
	memset(cDllPathBuff, 0, MAX_PATH);
	auto getdllpathstatus = GetModuleFileNameA((HMODULE)hDllHandle, cDllPathBuff,
		MAX_PATH);
	std::string strDllPathBuff = cDllPathBuff;
	auto nPathTruncLoc = strDllPathBuff.find_last_of('\\');
	strDllPathBuff = strDllPathBuff.substr(0, nPathTruncLoc + 1);//strDllPathBuff 为本动态库所在路径
	
	return strDllPathBuff;
}

extern "C" _declspec(dllexport) const char* start_x86(char* img_path, char* img_p)
{
	cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR); // 取消警告
	std::string dlldir = getdlldir();
	nlohmann::json results;
	
	std::string img_path_str = img_path;
	process(img_path_str, dlldir, results); // 处理函数,没放进来

	std::string results_s = results.dump(2);  //转储成字符串 可以使用dump(4)进行缩进
	char* return_char;
	return_char = strdup(results_s.c_str());
	return return_char;
}

C# 调用代码

首先将 dll与依赖放置在C# 项目的"Debug/Lib"下

|-- FungiIdentification_x64.dll

|-- FungiIdentification_x86.dll

|-- dish.onnx

|-- model_2.0.onnx

|-- x64_FungiIdentificationDependency

| |-- concrt140d.dll

| |-- msvcp140_1d.dll

| |-- msvcp140d.dll

| |-- onnxruntime.dll

| |-- opencv_world455d.dll

| |-- ucrtbased.dll

| |-- vcruntime140_1d.dll

| |-- vcruntime140d.dll

|-- x86_FungiIdentificationDependency

| |-- concrt140.dll

| |-- concrt140d.dll

| |-- msvcp140.dll

| |-- msvcp140_1d.dll

| |-- msvcp140d.dll

| |-- onnxruntime.dll

| |-- opencv_world455d.dll

| |-- ucrtbase.dll

| |-- ucrtbased.dll

| |-- vcruntime140.dll

| |-- vcruntime140d.dll

OpenCV 和 onnxruntime 都有 x64 和 x86 两个版本所以两个库的DLL与依赖都是不同的,所以分为两个文件夹放置 x64_FungiIdentificationDependency 和 x86_FungiIdentificationDependency

  • opencv_world455d.dll 在 OpenCV 编译包找到(注意版本)
  • 如果 onnxruntime 是用NuGet安装的,onnxruntime.dll 可以在 C++ 工程下找到(也要注意版本)
  • 像 concrt140d.dll 等这些 dll 是 C/C++ 运行时所需要的库,64位的在"C:\Windows\System32", 32位的在"C:\Windows\SysWOW64"
  • C++运行库的版本和使用的 Visual Studio版本有关,我用的是 VS2019,不同的VS版本使用的版本号可能不同
  • ONNX模型文件不分 x64 和 x86 ,可以共用
using Newtonsoft.Json.Linq;
using System;
using System.Runtime.InteropServices;

namespace ConsoleApp2._1
{

    class Program
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool SetDllDirectory(string lpPathName);

        [DllImport("Libs/FungiIdentification_x64.dll", CallingConvention = CallingConvention.Cdecl)]
        extern static IntPtr start_x64(string img_path);

        [DllImport("Libs/FungiIdentification_x86.dll", CallingConvention = CallingConvention.Cdecl)]
        extern static IntPtr start_x86(string img_path);

        static IntPtr start(string img_path)
        {
            if (Environment.Is64BitOperatingSystem)
            {
                return start_x64(img_path);
            }
            else
            {
                return start_x86(img_path);
            } 
        }
        
        static void setDllDirectory()
        {
            if (Environment.Is64BitOperatingSystem)
            {
                string dllpath = $"{AppDomain.CurrentDomain.BaseDirectory}/Libs/x64_FungiIdentificationDependency";  //BaseDirectory 有分隔符结尾
                bool ret = SetDllDirectory(dllpath);
                if (!ret) throw new System.ComponentModel.Win32Exception();
            }

            else
            {
                string dllpath = $"{AppDomain.CurrentDomain.BaseDirectory}/Libs/x86_FungiIdentificationDependency";  //BaseDirectory 有分隔符结尾
                bool ret = SetDllDirectory(dllpath);
                if (!ret) throw new System.ComponentModel.Win32Exception();

            }
        }

        static void Main(string[] args)
           {
            try
            {
                string img_path = @"D://微信图片_20250227203052.jpg";


                // 1. AnyCPU 测试(生成平台选择 AnyCPU)
                //    因为我的笔记本是 x64位,所以【去掉“首选32位”的勾选】
                setDllDirectory();
                IntPtr result = start(img_path);
                string result_str = Marshal.PtrToStringAnsi(result);


                // 2. x64 测试 (生成平台选择 x64)
                // string dllpath = $"{AppDomain.CurrentDomain.BaseDirectory}/Libs/x64_FungiIdentificationDependency";  //BaseDirectory 有分隔符结尾
                // bool ret = SetDllDirectory(dllpath);
                // if (!ret) throw new System.ComponentModel.Win32Exception();
                // IntPtr result_x64 = start_x64(img_path);
                // string result_str = Marshal.PtrToStringAnsi(result_x64);

                // 3. x86 测试(生成平台选择 x86)
                //string dllpath = $"{AppDomain.CurrentDomain.BaseDirectory}/Libs/x86_FungiIdentificationDependency";  //BaseDirectory 有分隔符结尾
                //bool ret = SetDllDirectory(dllpath);
                //if (!ret) throw new System.ComponentModel.Win32Exception();
                //IntPtr result_x86 = start_x86(img_path);
                //string result_str = Marshal.PtrToStringAnsi(result_x86);

                // 输出
                Console.WriteLine(result_str);
                JObject result_json = JObject.Parse(result_str);
                Console.WriteLine(result_json["err"]);
                Console.WriteLine(result_json["err_info"]);

                // ERR = 0; 正常
                // ERR = 1; 找不到图片
                // ERR = 2; 找不到dish.onnx
                // ERR = 3; 找不到model_2.0.onnx
                // ERR = 4: 没有检测到菌
            }
            catch (Exception ex)
            {
                Console.WriteLine($"ex:{ex}");
            }

            Console.ReadLine();
        }
    }
}

最后一个坑:

测试 Any CPU 时,如果你的电脑是x64,在选择生成平台时,需要去掉"首选32位"的选项

posted @ 2025-02-27 23:49  yaksa777  阅读(233)  评论(0)    收藏  举报