Une DLL développée en C++ utilisant OpenCV et onnxruntime doit être appelée depuis une application C# fonctionnant en mode AnyCPU, c'est-à-dire compatible à la fois avec les plateformes x86 et x64.
L'application C# doit placer la DLL C++ et ses dépendances dans un dossier "Libs" séparé plutôt que dans le dossier "Debug".
Difficultés techniques
OpenCV et onnxruntime existent en versions distinctes pour les architectures x86 et x64, qui ne sont pas interchangeables.
Quand la DLL C++ référence un fichier tel que "model.onnx", et que les fichiers sont placés dans "Debug/Libs", le système recherche ce fichier à partir de l'exécutable C# plutôt que depuis l'emplacement de la DLL, provoquant une erreur de fichier introuvable.
Configuration du système
- Système d'exploitation : Windows 11
- Environnement de développement : Visual Studio 2019
Solution proposée
- Créer deux DLL distinctes en C++ :
AnalyseChampignon_x64.dlletAnalyseChampignon_x86.dll- Chaque DLL expose des fonctions d'entrée différentes :
lancer_x64()etlancer_x86() - Utiliser une fonction
repertoireDLL()pour récupérer le chemin d'accès de la DLL et construire le chemin absolu du modèle ONNX
- Chaque DLL expose des fonctions d'entrée différentes :
- En C#, charger dynamiquement la DLL appropriée selon l'architecture système
- Créer une interface wrapper
lancer()qui sélectionne automatiquement la bonne version - Utiliser la fonction
SetDllDirectory()de "kernel32.dll" pour définir le dossier de dépendances approprié
- Créer une interface wrapper
Implémentation détaillée
Version 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 repertoireDLL()
{
HMODULE hModule = GetModuleHandleA("AnalyseChampignon_x64.dll");
char chemin[MAX_PATH];
memset(chemin, 0, MAX_PATH);
GetModuleFileNameA(hModule, chemin, MAX_PATH);
std::string cheminComplet = chemin;
size_t positionSeparateur = cheminComplet.find_last_of('\\');
return cheminComplet.substr(0, positionSeparateur + 1);
}
extern "C" __declspec(dllexport) const char* lancer_x64(char* cheminImage, char* parametres)
{
cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);
std::string dossierDLL = repertoireDLL();
nlohmann::json resultat;
std::string cheminImageStr = cheminImage;
traiterImage(cheminImageStr, dossierDLL, parametres, resultat);
std::string resultatStr = resultat.dump(2);
char* retour;
retour = strdup(resultatStr.c_str());
return retour;
}
Version 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 repertoireDLL()
{
HMODULE hModule = GetModuleHandleA("AnalyseChampignon_x86.dll");
char chemin[MAX_PATH];
memset(chemin, 0, MAX_PATH);
GetModuleFileNameA(hModule, chemin, MAX_PATH);
std::string cheminComplet = chemin;
size_t positionSeparateur = cheminComplet.find_last_of('\\');
return cheminComplet.substr(0, positionSeparateur + 1);
}
extern "C" __declspec(dllexport) const char* lancer_x86(char* cheminImage, char* parametres)
{
cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);
std::string dossierDLL = repertoireDLL();
nlohmann::json resultat;
std::string cheminImageStr = cheminImage;
traiterImage(cheminImageStr, dossierDLL, parametres, resultat);
std::string resultatStr = resultat.dump(2);
char* retour;
retour = strdup(resultatStr.c_str());
return retour;
}
Code C# pour l'appel de la DLL
Structure des dossiers de dépendances :
|-- AnalyseChampignon_x64.dll
|-- AnalyseChampignon_x86.dll
|-- champignon.onnx
|-- modele_2.0.onnx
|-- x64_DependancesAnalyse
| |-- concrt140d.dll
| |-- msvcp140_1d.dll
| |-- msvcp140d.dll
| |-- onnxruntime.dll
| |-- opencv_world455d.dll
| |-- ucrtbased.dll
| |-- vcruntime140_1d.dll
| |-- vcruntime140d.dll
|-- x86_DependancesAnalyse
| |-- concrt140.dll
| |-- concrt140d.dll
| |-- msvcp140.dll
| |-- msvcp140_1d.dll
| |-- msvcp140d.dll
| |-- onnxruntime.dll
| |-- opencv_world455d.dll
| |-- ucrtbase.dll
| |-- ucrtbased.dll
| |-- vcruntime140.dll
| |-- vcruntime140d.dll
using Newtonsoft.Json.Linq;
using System;
using System.Runtime.InteropServices;
namespace IntegrationDLL
{
class ProgrammePrincipal
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string cheminDossier);
[DllImport("Libs/AnalyseChampignon_x64.dll", CallingConvention = CallingConvention.Cdecl)]
extern static IntPtr lancer_x64(string cheminImage);
[DllImport("Libs/AnalyseChampignon_x86.dll", CallingConvention = CallingConvention.Cdecl)]
extern static IntPtr lancer_x86(string cheminImage);
static IntPtr lancer(string cheminImage)
{
if (Environment.Is64BitOperatingSystem)
{
return lancer_x64(cheminImage);
}
else
{
return lancer_x86(cheminImage);
}
}
static void configurerDossierDLL()
{
string cheminDeps;
if (Environment.Is64BitOperatingSystem)
{
cheminDeps = $"{AppDomain.CurrentDomain.BaseDirectory}Libs\\x64_DependancesAnalyse";
}
else
{
cheminDeps = $"{AppDomain.CurrentDomain.BaseDirectory}Libs\\x86_DependancesAnalyse";
}
bool resultat = SetDllDirectory(cheminDeps);
if (!resultat)
throw new System.ComponentModel.Win32Exception();
}
static void Main(string[] args)
{
try
{
string cheminImage = @"D:\\images\\echantillon.jpg";
// Configuration pour AnyCPU
configurerDossierDLL();
IntPtr resultatPtr = lancer(cheminImage);
string resultatStr = Marshal.PtrToStringAnsi(resultatPtr);
// Traitement du résultat
JObject resultatJson = JObject.Parse(resultatStr);
Console.WriteLine($"Code erreur: {resultatJson["erreur"]}");
Console.WriteLine($"Détails: {resultatJson["details_erreur"]}");
// Codes d'erreur:
// 0: Succès
// 1: Image non trouvée
// 2: Modèle dish.onnx manquant
// 3: Modèle model_2.0.onnx manquant
// 4: Aucun champignon détecté
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.Message}");
}
Console.ReadLine();
}
}
}
Remarque importante
Pour une compilation AnyCPU sur un système x64, il est nécessaire de désélectionner l'option "Préférer 32 bits" dans les paramètres de génération du projet.