>J'ai déjà essayé avec des dé-compilateurs mais il ne me trouve rien
Pouvez-vous nous envoyer l'assembly/Dll via un dépôt GitHub (ou autre) ?
>Mon objectif est d'une part de comprendre le code
De quel code s'agit-il ? Le code client de l'assembly ou le code de wrapping contenu/caché dans l'assembly/Dll SpinNET ?
Le code .NET me semble assez clair, bien que mal adroit à quelques endroits. (donc une API avec une conception assez correcte)
>et d'autre part de "le mettre à jour"
Mais vous n'avez pas le code source de SpinNET, ou j'ai compris de travers ?
>Je possède une en-tête qui regroupe (si j'ai bien regardé) toutes les autres en-têtes.
OK, mais c'est pour du C++, vous n'avez pas besoin de ça si vous utilisez SpinNET.
>incluant dans le .cpp, le nom de l'en-tête,
Les .h ne contiennent pas le code, juste les déclarations des types et fonctions (à moins d'être un bibliothèque "header-only", mais j'en doute fortement).
Pour utiliser le code binaire de la Dll, il faut faire des appels à ces fonctions/types dans votre code C++/CLI, ou plus difficilement/aléatoirement directement depuis du C#.
>serait-il possible de retrouver leurs fonctions en C#
Oui, mais c'est potentiellement très compliqué (fonction du compilateur C++ qui a généré la Dll Native, utilisation potentiel de pointeur intransferable directement en .NET, etc...). Tout cela n'existe pas si vous communiquez directement avec un assembly/Dll .NET.
Je vous guide depuis le "début" sur la création de wrapper C++/CLI mais s'il existe déjà un assembly .NET, ces wrappers sont inutiles.
Concrètement, c'est quoi les inputs de votre projet et les "limitations/évolutions" que vous devez 'faire sauter/implémenter" ???
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
Pour résumé, je possède des bibliothèques, en-têtes avec une version 3.3. J'ai trouvé sur internet un sdk avec la version 3.0 (include, librairie, dll, fichier debug), mon but est de réussir à coder avec les fichiers du 3.3 sans dépendre du sdk.
Question con, vous êtes sûr que cette bibliothèque n'est pas en Open Source ?
La version 3.0 contient un assembly .NET, donc ? Vous êtes sûr que la version 3.3 n'en dispose pas ?
Une assembly .NET, ça n'a pas besoin de .h ou d'un SDK pour être directement utilisable dans un autre projet/exécutable (c'est l'un des gros avantages de .NET).
Pouvez-vous nous communiquer les fichiers de la version 3.3 (via GitHub ou autre) ?
Avant de faire des "bridges" qui correspondent aux classes du SDK, je vous conseille de faire quelques wrappers des classes de la version 3.3.
Contrairement aux bridges, les wrappers se résument à beaucoup de copier-coller, comme mon exemple de code précédemment posté.
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
Pour avoir accès au code source, il faut un abonnement.
Voici tout les fichiers qui va avec la version 3.3 (il faut dézipper dans le dossier implémentation en fonction de votre ordinateur) dans le lien ci-dessous:
>Pour avoir accès au code source, il faut un abonnement.
A plusieurs centaine d'€ par jour la prestation d'un développeur, l'abonnement est très vite rentabilisé, mais bon.
OK, c'est pas vraiment plus clair mais c'est du concret.
Avant d'aller plus loin. Si je comprends bien, vous cherchez à avoir une API C# identique ou proche de celle de SpinNET mais en utilisant la version 3.3 de GenICam. Si le packaging de SpinNet est bien fait et qu'il n'y a pas de problème de rétro-compatibilité (ce que doit indiquer le 3 de 3.0 et de 3.3), il est tout à fait possible que cet assembly .NET s'upgrade "tout seul" (via fichier de configuration, fichier de patching dans le GAC, etc...).
Avez-vous compulsé la documentation de cet assembly sur les différents paramétrages qu'il support ?
Il est peut-être possible d'upgrader SpinNet sans la moindre ligne de code (donc moins de boulot et moins de bug).
Même si SpinNET n'est pas packagé pour s'upgrader, il est assez probable qu'il soit désassemblable et que la mise à niveau des sources ne demanderait que de très faibles modification par rapport à tout reprendre from scratch.
Je n'ai pas vu de "GC.h" que vous nous avez posté dans ce sujet. Le zip est vaste et il contient lui-même des zip dans tout les coins. Ca ne facilite pas la recherche. Je n'ai fait que jeter un coup d’œil rapide au contenu du zip car lire la documentation prendrait beaucoup de temps. (J'ai quand même vu des ponts vers JAVA et Python, ce qui serait peut-être plus simple que le C++ si vous connaissez bien l'un de ces 2 langages.)
Mais j'ai rapidement eu accès à 3 .h qui me semblent être l'API "de départ" : PFNC.H, GenDC.h et GenTL.h.
Si vous voyez des .h plus pertinents, faites le moi savoir.
(EDIT: après avoir regardé l'ensemble des .dll, il est clair que vous devriez faire un recensement de celles-ci et de faire un graphe de dépendance entre elles et de récupérer le ou les .h correspondant au niveau d'abstraction que vous voulez récupérer déduit de ce graphe)
Clairement, l'API de GenICam (basée sur ma vue des 3 fichiers .h) est loin d'être aussi "conviviale" que celle de SpinNET.
Il y a 2 voix pour completer ce gap. Soit vous êtes à laisse en C++/CLI et vous faites les bridges directement en C++, soit vous êtes plus à laisse en .NET et vous ne faite que des wrappers en C++/CLI et vous implémenter les bridges en .NET en s'appuyant sur ces wrappers.
Vous devriez cherchez la Dll permettant d'avoir le moins de travail entre son API et votre API cible (SpinNET ?).
Exemple de récupération dans du code C++/CLI du contenu de ces 3 .h :
PFNC.H est un header de "bas niveau" (utilisé par les autres) et ne définit qu'un monceau de constantes, un enum et déclare+defini(inline) 2 fonctions libres "utilitaires" (pas besoin du moindre .lib ou .dll pour s'en servir, c'est une espèce de bibliothèque "header only"). Soit vous en avez besoin que dans le code C++ (implémentation de bridge) et dans ce cas, juste l'inclure dans le .cpp qui l'utilise suffit. Si vous voulez que du code .NET puisse accéder à ces constantes/enum/fonction libre, il faudra un peu plus de travail.
Pour que les constantes à base de #define et les enum soient visible en .NET facilement, il faut un peu ruser :
Pour ce qui est des 2 fonctions libres, comme elles utilisent des pointeurs et des classes natives dans leur signature, le plus simple, c'est de faire un wrappers vite fait :
#undef _MANAGED
#include "PFNC.h"
#define _MANAGED
namespace GENAPI_NAMESPACE_Wrappers
{
#include "PFNC.h"
public ref class PFNC abstract sealed
{
public static System::String^ GetPixelFormatName(PfncFormat format)
{
return marshal_as(::GetPixelFormatName((int)format));
}
public static System::String^ GetPixelFormatDescription (PfncFormat format)
{
return marshal_as(::GetPixelFormatDescription ((int)format));
}
}
}
GenDC.H, c'est un peu le même topo que PFNC.h, donc les mêmes astuces pour les #define et les enum. Il y a pas mal de typedef que les mécanismes d'alias de .NET permettent "d'émuler". Les structures et les unions sont des POCO sans aucun comportement, donc aussi facile à "émuler" (si nécessaire). L'équivalent des template C++, c'est les génériques .NET. Comme PFNC.h, pas besoin du moindre .lib ou .dll pour s'en servir, c'est une espèce de bibliothèque "header only".
GenTL.h est, lui, est un vrai header "chapeau" d'une Dll. Il permet d'utiliser les fonctions libres implémentées dans une Dll. A partir de la ligne 695, il spécifie l'API C (pas C++, même si ce fichier n'est utilisable qu'avec un compilateur C++), de la Dll et vous pouvez faire la même tambouille que pour les fonctions libre de PFNC.h. Les ruses pour les #define, les enum ... fonctionneront aussi bien dans ce cas de figures.
Donc, en résumé, faire le graphe des Dll, voir les .h chapeaux de ces Dll pour vérifier le niveau d'abstraction exportée par cette Dll (en commençant par le plus haut niveau, la Dll qui dépend de toutes les autres). Le but est d'utiliser l'API (Dll) ayant le plus de point commun avec l'API C# que vous cherchez à reproduire, pour minimiser le travail d'implémentation des bridges.
Une fois la Dll/.h choisi, il "suffit" de faire le même travail de carrossage du contenu du .h pour qu'il soit utilisable depuis .NET, si on fait les bridges en .NET, soit juste inclure simplement le .h et implémenté les bridges .NET directement en C++/CLI.
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
Pour faire clairement ça, j'ai recommencé depuis le début.
Changer juste les dll semblent compliquer (pas dans le remplacement des fichiers) mais du fait que les dll utiliser dans le SpinNET utiliser des version debug.
Je me suis pencher tout d'abord sur le GenTl et j'ai trouvé ca:
#include <iostream>
#include <sstream>
#include <_GenICamVersion.h>
#include <GenICam.h>
#include <GenICamFwd.h>
#include <GenICamVersion.h>
#include <GenApi/GenApi.h>
#include <CLProtocol/CLProtocol.h>
#include <CLProtocol/CLPort.h>
#include <FirmwareUpdate/FirmwareUpdater.h>
#include "GenTL.h"
#include "GenDC.h"
#include "PFNC.h"
using namespace GENAPI_NAMESPACE;
using namespace GENICAM_NAMESPACE;
using namespace GenApi_3_3;
using namespace GenApi;
using namespace GenTL;
using namespace std;
//Main
int main() {
GCInitLib(); //Initialize GenTL Producer
TL_HANDLE hTL = OpenTL(); //Retrieve TL Handle
IF_HANDLE hIface = OpenFirstInterface(hTL); //Retrieve first Interface Handle
DEV_HANDLE hDevice = OpenFirstDevice(hIface); //Retrieve first Device Handle
DS_HANDLE hStream = OpenFirstDataStream(hDevice); //Retrieve first Data Stream
CloseDataStream(hStream); //Close Data Stream
CloseDevice(hDevice); //Close Device
CloseInterface(hIface); //Close Interface
CloseTL(hTL); //Close System module
GCCloseLib(); //Shutdown GenTL Producer
}
TL_HANDLE OpenTL( void )
{
TLOpen(hTL);
}
IF_HANDLE OpenFirstInterface(hTL)
{
TLUpdateInterfaceList(hTL);
TLGetNumInterfaces(hTL, NumInterfaces);
if (NumInterfaces > 0)
{
// First query the buffer size
TLGetInterfaceID(hTL, 0, IfaceID, &bufferSize);
// Open interface with index 0
TLOpenInterface(hTL, IfaceID, hNewIface);
return hNewIface;
}
}
DEV_HANDLE OpenFirstDevice(hIF)
{
IFUpdateDeviceList(hIF);
IFGetNumDevices(hTL, NumDevices);
if (NumDevices > 0)
{
// First query the buffer size
IFGetDeviceID(hIF, 0, DeviceID, &bufferSize);
// Open interface with index 0
IFOpenDevice(hIF, DeviceID, hNewDevice);
return hNewDevice;
}
}
DS_HANDLE OpenFirstDataStream(hDev)
{
// Retrieve the number of Data Stream
DevGetNumDataStreams(hDev, NumStreams);
if (NumStreams > 0)
{
// Get ID of first stream using
DevGetDataStreamID(hdev, 0, StreamID, buffersize);
// Instantiate Data Stream
DevOpenDataStream(hDev, StreamID, hNewStream);
}
}
void CloseDataStream(hStream)
{
DSClose(hStream);
}
void CloseDevice(hDevice)
{
DevClose(hDevice);
}
void CloseInterface(hIface)
{
IFClose(hIface);
}
void CloseTL(hTL)
{
TLClose(hTL);
}
Plusieurs variables me sont revenus comme non défini.
Passer par un wrapper m'a l'air plutot pas mal.
J'ai également trouver un wrapper.hh dans les fichiers
#ifndef _LOG4CPP_WRAPPER_HH
#define _LOG4CPP_WRAPPER_HH
#include <log4cpp/Category.hh>
#include <log4cpp/NDC.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/PropertyConfigurator.hh>
#if defined (_WIN32)
#include <log4cpp/Win32DebugAppender.hh>
#endif
LOG4CPP_NS_BEGIN
//! A function table which is bound to log4cpp's Category class
typedef struct {
Category& (*getInstance)(const std::string& name);
std::vector<Category*>* (*getCurrentCategories)();
Category* (*exists)(const std::string& name);
Category& (*getRoot)();
void(*shutdown)();
void (Category::*logva)(Priority::Value priority, const char* stringFormat, va_list va);
bool (Category::*isInfoEnabled)();
bool (Category::*isWarnEnabled)();
bool (Category::*isDebugEnabled)();
void (Category::*setPriority)(Priority::Value priority);
void (Category::*addAppender)(Appender* appender);
void (Category::*removeAppender)(Appender* appender);
} category_t;
//! A function table which is bound to log4cpp's PatternLayout class
typedef struct {
PatternLayout* (*create)();
void (PatternLayout::*setConversionPattern)(const std::string& conversionPattern);
void(*destroy)(PatternLayout* object);
} pattern_layout_t;
//! A function table which is bound to log4cpp's PropertyConfigurator class
typedef struct {
void(*configure)(std::istream& initStream);
} property_configurator_t;
//! A function table which is bound to log4cpp's NDC class
typedef struct {
void(*push)(const std::string& message);
std::string(*pop)();
} ndc_t;
#if defined (_WIN32)
//! A function table which is bound to log4cpp's Win32DebugAppender class
typedef struct {
Appender *(*create)(const std::string& name);
} win32_debug_appender_t;
#endif
//! A function table which is bound to log4cpp's Appender class
typedef struct {
Appender *(*createFileAppender)(const std::string& name, const std::string& fileName, bool append, mode_t mode);
void (Appender::*setThreshold)(Priority::Value threshold);
void (Appender::*setLayout)(Layout* layout);
} appender_t;
//! A wrapper which bridges to log4cpp functionality.
typedef struct {
category_t Category;
pattern_layout_t PatternLayout;
property_configurator_t PropertyConfigurator;
ndc_t NDC;
#if defined (_WIN32)
win32_debug_appender_t Win32DebugAppender;
#endif
appender_t Appender;
} wrapper_t;
//! A functionality wrapper (= a set of function tables) which is exported.
extern "C" LOG4CPP_EXPORT const LOG4CPP_NS::wrapper_t Wrapper;
LOG4CPP_NS_END
#endif // _LOG4CPP_WRAPPER_HH
"wrapper.hh", c'est juste un .h qui permet d'utiliser plus facilement Log4cpp, qui est un framework de "log" pour le C++, donc n'a pas vraiment d'intérêt pour le "bridging" .NET (à moins de vouloir que le code Natif et .NET log dans les mêmes fichiers au même format, mais bon).
Pour "GenTl", c'est clairement un code foireux par l'usage complètement incohérent de putatives variables globales. Si ces énormes erreurs ne vous semblent pas évidentes, pensez à revoir un peu le codage en C++ avant de continuer plus avant.
Malgré ses énormes erreurs (et lourdeur) ce code, il peut montrer un designe d'API .NET très facile à faire à partir de l'API C utilisée dans ce code.
Un truc du genre :
public ref class GenTL_Producer
{
public TL_Handle^ th;
public GenTL_Producer()
{
GCInitLib();
th = gcnew TL_Handle(this);
}
public ~GenTL_Producer()
{
delete th;
GCCloseLib();
}
}
public ref class TL_Handle
{
private GenTL_Producer^ gp;
public Interface_Handle^ ih;
internal TL_HANDLE handle;
public TL_Handle(GenTL_Producer gp_)
{
gp = gp_;
OpenTL(handle);
ih = gcnew Interface_Handle(this);
}
public ~TL_Handle()
{
delete ih;
CloseTL(handle);
}
}
public ref class Interface_Handle
{
private Device_Handle^ dh;
public TL_Handle^ th;
internal IF_HANDLE handle;
public Interface_Handle(TL_Handle^ th_)
{
th = th_;
handle = OpenFirstInterface(th.handle);
dh = gcnew Device_Handle(this);
}
public ~Interface_Handle()
{
delete dh;
CloseInterface(handle);
}
}
public ref class Device_Handle
{
private Data_Stream^ ds;
public Interface_Handle^ ih;
internal DEV_HANDLE handle;
public Device_Handle(Interface_Handle^ ih_)
{
ih = ih_;
handle = OpenFirstDevice(ih.handle);
ds = gcnew Data_Stream(this);
}
public ~Device_Handle()
{
delete ds;
CloseDevice(handle);
}
}
public ref class Data_Stream
{
private Device_Handle^ dh;
private DS_HANDLE handle;
public Data_Stream(Device_Handle^ dh_)
{
dh = dh_;
handle = OpenFirstDataStream(dh.handle);
}
public ~Data_Stream()
{
CloseDataStream(handle);
}
}
Juste en instanciant "GenTL_Producer" (pensez à implémenter un DP type Singleton), vous disposez de l'ensemble des objets de l'API Native accessible depuis du code .NET.
Ici, on a fait qu'"objectiser" l'API C pour quelle soit utilisable en .NET.
>les dll utiliser dans le SpinNET utiliser des version debug.
Mais si vous avez le code source de SpinNET, le plus simple, c'est de faire quelques modifications dans le paramétrage de génération, voire faire quelques modifications/ajout pour utiliser les nouvelles fonctionnalités de la version 3.3 du package.
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
Excuser moi de mes imprécisions, Je possède les en-têtes et dll + librairie, comprend les deux packages.
DotNET ? Un assembly managé, avec le source ? c'est SpinNet ou GenICam ? Cela correspond seulement au SpinNet.
Leurs codes d'exemples doivent avoir un bien meilleur code que celui de leur documentation. En effet, le code pour le contrôle de la caméra est plutôt clair mais il utilise une Dll dont je n'ai pas accès à leur contenu (sauf via "trouver la définition", mais pour refaire ce dernier il me manque trop d'éléments). (Leur exemple ne marche que dans leur package. Je n'ai pas réussit à en refaire un à l'identique pour essayer (bien sur en réutilisant leur dll déjà faite).
Je dois réaliser un programme qu'avec Genicam. J'ai donc créer un cpp regroupant (quasi) toutes les fonctions des en-têtes fournis et j'ai repris un programme d'exécution en cs pour faire les essais. Je rencontre un problème dans mon programme cs, j'appelle une classe INodeMap que j'instancie par le mot nodeMap et j'essaye d'utiliser la fonction GetNode, mais il ne la reconnait pas.
Votre wrapper C++/CLI, il sera tout aussi .NET et "en plus" que SpinNET.
Faudrait pas se mettre des bâtons dans les roues pour que dal.
Avoir le code de SpinNet (via desassemblage ou pas) permettrait d'avoir des exemples concrets.
La déclaration dans le .h, c'est juste la déclaration d'une interface C++, "utilisable" quand C++, et encore, par directement (il faut des classes dérivées et des API C++ qui l'utilise).
Ce n'est donc "utilisable" que dans une implémentation de wrapper C++/CLI, pas dans du code C#.
Le code C++, c'est juste une déclaration d'une classe native (qui n'entre pas en collision de nom avec la déclaration dans le .h grace vraisemblablement à un espace de nom judicieusement planqué dans la MACRO "GENICAM_INTERFACE" ). C'est donc tout aussi inutilisable en C# que l'interface déclarée dans le .h.
Vos classes wrapper devraient être des classes .NET/managée (public ref class Toto {...}).
Et n'utilisez pas des pointeurs dans l'API (champs, propriétés, types de retour de méthodes, paramètres de méthodes) publiée par vos classes managées, si elles doivent être appelable depuis du C#.
Dans votre code C#, il y une incohérence frappante.
Si la méthode "GetNode" renvoie un INode* ( et non un INodeMap ou un INodeMap*) pourquoi voulez-vous qu'il en soit autrement en C# ? Pourquoi "iAquiMode" est un INodeMap et pas un INode ???
Donc, un bout de code pour commencer les wrappers .NET (j'ai utilisé un suffixe "W" et non un namespace pour que la distinction natif/managé soit plus "lisible"):
public ref class INodeW
{
}
public ref class INodeMapW
{
GENICAM_INTERFACE GENAPI_DECLABSTRACT INodeMap* nativeINodeMap = null;
public :
INodeW^ GetNode(String^ Name)
{
//ici la tambouille pour convertir une String^ en gstring "name_"
GENICAM_INTERFACE GENAPI_DECLABSTRACT INode* p_node = nativeINodeMap(name_);
//ici la tambouille pour créer un InodeW nodeWrapper, à partir d'un INode* p_node, par exemple en le passant en paramètre du constructeur d'un InodeW
return nodeWrapper;
}
}
Les détails comme comment initialiser le champ "nativeINodeMap" de l'instance de classe INodeMapW, etc... c'est fonction de comment est conçu l'API native et comment vous comptez concevoir l'API managée.
>Pour les dossiers souhaiter vous celui de Spin, Genicam ou les deux ?
Mettez tout dans un dépôt Git, on fera le tri plus tard.
- Edité par bacelar 21 juin 2021 à 20:52:15
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
iAquiMode est un INodeMap car la fonction qu'il utilise fait parti de l'en-tête INodeMap et non INode. Merci pour ce bout de code, je vois où j'ai fauté dans la classe.
Puis-je savoir pourquoi la fonction de création d'objet est égale à "null" ?
>iAquiMode est un INodeMap car la fonction qu'il utilise fait parti de l'en-tête INodeMap et non INode.
Et ?
L'endroit où une fonction est utilisée ou déclarée ou définie n'indique en rien le type d'une variable ou du type de retour d'une fonction.
C'est la ligne de déclaration de la variable ou de la fonction qui indique leur type (et leur définition doivent correspondre à cette déclaration).
>Puis-je savoir pourquoi la fonction de création d'objet est égale à "null" ?
Oups, le mélange de code C++ natif et managé ne me réussit pas. C'est "__nullptr" qu'il utiliser.
Ce n'est pas une "fonction de création d'objet" mais la déclaration d'un champ privé à la classe .NET INodeMapW.
Le but d'un wrapper est "d'enrober" un objet natif par une enveloppe utilisable en managé. L'instance de la classe INodeMapW est l'enveloppe et son champ "nativeINodeMap" est l'objet natif qui est enveloppé.
Comme je ne connais pas le pattern de création implémenté par l'API native (constructeur "classique" (très peu probable), singleton, factory, etc...), j'ai fait au plus simple. Vous devez initialiser ce champ avec l'objet natif et l'utilisation de cette valeur permet de facilement détecter des erreurs de type valeurs non initialisées.
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
Voici ce que j'ai fait mais une erreur persiste, la E0109 sur le morceau nativeINodeMap(name_)
"l'expression qui précède les parenthèses de l'appel apparent doit avoir le type defonction (pointeur-à-)"
//Wrapper.cpp
#include <string>
#include <iostream>
#include <vcclr.h>
#include "pch.h"
#include "EnTete.h" //En-tête Genicam
//Raccouci d'appel
using namespace GENAPI_NAMESPACE;
using namespace GENICAM_NAMESPACE;
using namespace std; //MarshalString
using namespace System; //MarshalString
// Wraper
namespace WrapperNET
{
//Sous-partie Wrapper
namespace GenApi
{
//INode
public ref class Node
{
//Création d'objet de la classe INode
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INode * nativeINode = __nullptr;
};
//INodeMap
public ref class NodeMap
{
//Création d'objet de la classe INodeMap
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INodeMap * nativeINodeMap = __nullptr;
public:
//Retrieves the node from the central map by Name
Node^ GetNode(String^ Name)
{
//Converstion gcstring en String^ et de Name en name_
gcstring name_;
String^ iName = gcnew String(name_.c_str());
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INode * p_node = nativeINodeMap(name_);
//Création de nodeWrapper
Node ^ nodeWrapper(INode * p_node);
//Retour de la fonction GetNode
return nodeWrapper(p_node);
}
};
}
}
Comme je l'ai déjà indiqué, "nativeINodeMap" n'est pas une fonction/functor/lambda mais un simple champ (pour "nativeINode", c'est pareil). Ce n'est pas un moyen de créer l'objet qu'il référence. cf. "pattern de création" de mon post précédent.
Attention au ' " include "pch.h" ', ligne 7, normalement, vous devriez le mettre au tout début du fichier, et mettre dans le fichier pch.h les #include de tous les fichiers communs à tous les .cpp (<string> ?, <vcclr.h> ?, etc...). C'est juste pour l'optimisation du temps de compilation, pas très grave pour l'instant.
Ligne 40 à 42, c'est presque l'inverse qu'il faut faire. Le code .NET appelant la méthode GetNode passe une String^ en paramètre. Vous devez la convertir en gcstring pour appeler la fonction native "GetNode" de l'objet référencé dans le champ "nativeINodeMap".
En gros, à l'arrache, si on considère que l'INodeMap se récupère via un Singleton, ça donnerait un code un peu comme celui qui suit (pas thread-safe, etc...) :
//Wrapper.cpp
#include "pch.h"
#include "EnTete.h" //En-tête Genicam
#include <string>
#include <iostream>
#include <vcclr.h>
//Raccouci d'appel
using namespace GENAPI_NAMESPACE;
using namespace GENICAM_NAMESPACE;
using namespace std; //MarshalString
using namespace System; //MarshalString
// Wraper
namespace WrapperNET
{
internal gcstring NetString2gcstring(String^ name)
{
gcstring retval{};
// Les bidouilles avec MarshalString pour remplir "retval" avec le contenu de "name"
return retval;
}
//Sous-partie Wrapper
namespace GenApi
{
//INode
public ref class Node
{
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INode * nativeINode = __nullptr;
//Peut-être plus pertinent d'utiliser une référence ou un smart-pointer à la place d'un pointeur "nu"
//Et d'utiliser la liste d'initialisation que le corps du constructeur
//à vérifier la compatibilité avec une classe managée
Node(GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INode * nativeINode_)
{
nativeINode = nativeINode_;
}
};
//INodeMap
public ref class NodeMap
{
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INodeMap * nativeINodeMap = __nullptr;
bool CheckNativeSingleton()
{
if(nativeINodeMap == __nullptr)
{
nativeINodeMap = GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INodeMap::GetInstance();
}
return (nativeINodeMap != __nullptr);
}
public:
//Retrieves the node from the central map by Name
Node^ GetNode(String^ Name)
{
if(CheckNativeSingleton())
{
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INode * p_node = nativeINodeMap.GetNode(NetString2gcstring(Name));
if(p_node != __nullptr)
{
//Création de nodeWrapper
Node^ nodeWrapper = gcnew Node(p_node);
//Retour de la fonction GetNode
return nodeWrapper;
}
else
{
throw .... //fonction de comment vous voulez que le code .NET gère les erreurs Runtime
}
}
else
{
throw .... //fonction de comment vous voulez que le code .NET gère les erreurs Runtime
}
}
};
}
}
- Edité par bacelar 22 juin 2021 à 13:20:41
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
Merci, je vais regarder cela. d'ou vient le INodeMap:: GetInstance(); Je ne le trouve pas et le return nodeWrapper; ne marche pas comme ca, il me le met en erreur. Pour MarshalString J'ai deux fonctions, je vais essayer de les retrouver.
J'ai trouvé que les classes abstraites ne sont pas instanciable mais que les fonctions pures le sont (J'y arrive pas totalement encore).
C'est mon hypothèse de départ : "l'INodeMap se récupère via un Singleton"
Une manière "classique" d'implémenter un Singleton, c'est de créer une fonction statique "GetInstance" qui retourne l'unique instance de la classe (cf. le Design Pattern Singleton).
Si vous avez un code C++ natif d'exemple qui créer/récupérer une instance d'INodeMap, montrez-le nous, on trouvera facilement le Design Pattern de création que ce code utilise.
Le plus simple, c'est que vos wrappers utilisent les mêmes Design Pattern de création que le code C++ natif.
>que les classes abstraites ne sont pas instanciable
C'est la définition même du classe abstraite, c'est aussi pour cela que je parle des Design Pattern de création utilisés car vous ne pouvez pas simplement instancier une de ces classes pour en avoir une instance. D'où mon hypothèse d'un singleton, mais ça peut être une fonction "factory", une suite d'appel "builder", etc... . Le plus simple, comme déjà écrit ci-avant, donnez-nous un exemple "C++ Natif" qui fonctionne, on trouvera facilement quel Design Pattern de création est en action.
>mais que les fonctions pures le sont
??? Une "fonction", en terme strict, ça ne s'instancie pas, contrairement à des functor, donc je ne sais pas de quoi vous parler.
Vous voulez dire "appelable" ?
Si oui, c'est normal, c'est le "contrat" que donne l'interface, toute classe "instanciable" qui implémente l'interface s'engage à implémenter toutes ces fonctions virtuelle pure, sinon, elle n'est pas instanciable et cela sera uniquement les classes dérivées qui les implémentent toutes qui seront instanciables.
Pour "internal", enlevez-le. Normalement, c'est la valeur par défaut, mais il y a peut-être un peu de subtilité que je n'ai plus en tête.
- Edité par bacelar 22 juin 2021 à 15:05:09
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
//INodeMap.h
#ifndef GENAPI_INODEMAP_H
#define GENAPI_INODEMAP_H
#include <Base/GCBase.h>
#include <GenApi/INode.h>
#include <GenApi/IPort.h>
#include <GenApi/IPortStacked.h>
#include <GenApi/Synch.h>
#include <GenApi/ConcatenatedWrite.h>
#ifdef _MSC_VER
# pragma warning ( push )
# pragma warning ( disable : 4251 ) // XXX needs to have dll-interface to be used by clients of class YYY
#endif
namespace GENAPI_NAMESPACE
{
//*************************************************************
// INodeMap interface
//*************************************************************
/**
\brief Interface to access the node map
\ingroup GenApi_PublicInterface
*/
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INodeMap
{
//! Retrieves all nodes in the node map
virtual void GetNodes(NodeList_t &Nodes) const = 0;
//! Retrieves the node from the central map by Name
virtual INode* GetNode( const GENICAM_NAMESPACE::gcstring& Name) const = 0;
//! Invalidates all nodes
virtual void InvalidateNodes() const = 0;
//! Connects a port to a port node with given name
virtual bool Connect( IPort* pPort, const GENICAM_NAMESPACE::gcstring& PortName) const = 0;
//! Connects a port to the standard port "Device"
virtual bool Connect( IPort* pPort) const = 0;
//! Connects a port to a port node with given name
virtual bool Connect(IPortStacked* pPort, const GENICAM_NAMESPACE::gcstring& PortName) = 0;
//! Connects a port to the standard port "Device"
virtual bool Connect(IPortStacked* pPort) = 0;
//! Get device name
/*! The device name identifies a device instance, e.g. for debugging purposes.
The default ist "Device". */
virtual GENICAM_NAMESPACE::gcstring GetDeviceName() = 0;
//! Fires nodes which have a polling time
virtual void Poll( int64_t ElapsedTime ) = 0;
//! Returns the lock which guards the node map
virtual CLock& GetLock() const = 0;
//! Get the number of nodes in the map
virtual uint64_t GetNumNodes() const = 0;
//! Parse all Swissknife equations
virtual bool ParseSwissKnifes( GENICAM_NAMESPACE::gcstring_vector *pErrorList = NULL ) const = 0;
//! Create a new write concatenator object
virtual CNodeWriteConcatenator *NewNodeWriteConcatenator() const = 0;
//! Execute the transaction
virtual bool ConcatenatedWrite(CNodeWriteConcatenator *, bool featureStreaming = true, GENICAM_NAMESPACE::gcstring_vector *pErrorList = NULL) = 0;
};
}
#ifdef _MSC_VER
# pragma warning ( pop )
#endif
#endif // ifndef GENAPI_INODEMAP_H
Voici le INodeMap.h.
Je pense que le GetInstance() équivaut au InvalidateNodes() ?
//Wrapper.cpp
#include "pch.h"
#include "EnTete.h" //En-tête Genicam
#include <string>
#include <iostream>
#include <vcclr.h>
//Raccouci d'appel
using namespace GENAPI_NAMESPACE;
using namespace GENICAM_NAMESPACE;
using namespace std; //MarshalString
using namespace System; //MarshalString
using namespace Runtime::InteropServices;
// Wrapper
namespace WrapperNET
{
gcstring NetString2gcstring(String^ name)
{
gcstring retval{};
const char* chars = (const char*)(Marshal::StringToHGlobalAnsi(name)).ToPointer();
retval = chars;
Marshal::FreeHGlobal(IntPtr((void*)chars));
return retval;
}
//Sous-partie Wrapper
namespace GenApi
{
//INode
public ref class Node
{
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INode * nativeINode = __nullptr;
//Peut-être plus pertinent d'utiliser une référence ou un smart-pointer à la place d'un pointeur "nu"
//Et d'utiliser la liste d'initialisation que le corps du constructeur
//à vérifier la compatibilité avec une classe managée
Node(GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INode * nativeINode_)
{
nativeINode = nativeINode_;
}
};
//INodeMap
public ref class NodeMap
{
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INodeMap * nativeINodeMap = __nullptr;
bool CheckNativeSingleton()
{
if (nativeINodeMap == __nullptr)
{
nativeINodeMap = GENAPI_DECL_ABSTRACT INodeMap::GetInstance();
}
return (nativeINodeMap != __nullptr);
}
public:
//Retrieves the node from the central map by Name
Node^ GetNode(String^ Name)
{
if (CheckNativeSingleton())
{
GENICAM_INTERFACE GENAPI_DECL_ABSTRACT INode * p_node = nativeINodeMap.GetNode(NetString2gcstring(Name));
if (p_node != __nullptr)
{
//Création de nodeWrapper
Node^ nodeWrapper = gcnew Node(p_node);
//Retour de la fonction GetNode
return nodeWrapper;
}
/* else
{
throw .... //fonction de comment vous voulez que le code .NET gère les erreurs Runtime
}
*/ }
/* else
{
throw ....//fonction de comment vous voulez que le code .NET gère les erreurs Runtime
}
*/ };
};
}
}
Erreurs :
GetInstance() > pas de membre
nativeINodeMap de nativeINodeMap.GetNode(NetString... > l'expression doit avoir un type de classe
A Node^ nodeWrapper = gcnew Node(p_node) > il indique qu'il est inacessible
Je suis en train de voir pour le NetString2gcstring il est très probablement faux.
Je ne vous demande pas la déclaration complète de l'interface, je vous demande un exemple d'utilisation de cette interface.
Ca doit trainer dans la documentation ou dans des projets d'exemple du SDK.
>Je pense que le GetInstance() équivaut au InvalidateNodes() ?
WTF ?!?
GetInstance, ça demande une instance d'une classe, donc c'est une fonction statique.
InvalidateNodes, c'est marqué dans les commentaires juste au dessus, c'est une méthode (fonction d'instance virtuelle si vous préférez) invalide les nœuds ce cette Map (captain obvious).
Avant de faire des Wrappers .NET il faut maîtriser un minimum l'API Native du bidule.
Ligne 25, vous êtes sûr que la classe "GENICAM_NAMESPACE::gcstring" copie bien le contenu de la chaîne en interne lors d'une affectation depuis un "const char *" ??? Parce que vous supprimez/désallouez/libérez ce pointeur ligne 26. Si c'est pas le cas, vous allez faire planté votre programme.
>GetInstance() > pas de membre
Je le répète pour la n-ième fois, il faut adapter le code au Design Pattern de création de l'API Native.
>nativeINodeMap de nativeINodeMap.GetNode(NetString... >
Faudrait avoir le message d'erreur complet, SVP.
Mais comme il y a ces cochonneries de MACRO qui trainent, je remplacerais les lignes suivantes, pour ne pas avoir d'ambiguïté d'interprétation du "*" des pointeurs "nu" :
Voici une partie du programme en CSharp : avec le premier appel:
static int AcqImg(IManagedCamera cam, INodeMap nodeMap) //IManagedCamera propre au sdk
{
int result = 0;
try
{
IEnum iAcquisitionMode = nodeMap.GetNode<IEnum>("AcquisitionMode"); //IEnum, interface fille de IValue, SDK
if (iAcquisitionMode == null || !iAcquisitionMode.IsWritable) //INode pour le IsWritable (inline bool)
{
Console.WriteLine("Unable to set acquisition mode to continuous (node retrieval). Aborting...\n");
return -1;
}
...
Pour la ligne 26, j'indique d'abord le return avant de décharger ?
>Le nativeINodeMap est souligné rouge, Erreur E0153
Compilez, SVP, les "souligné rouge", c'est la compilation en tâche de fond : ce n'est pas fiable et leur numéro d'erreur ne sont pas correctement référencées.
Et mettez systématiquement le "*" des pointeurs collé au nom du type :
>Voici une partie du programme en CSharp : avec le premier appel:
Je vous demande un code d'exemple en C++ Natif et vous me fournissez un code CSharp ?!?!?
D'une, les Design Pattern de création peuvent être tout à fait différent entre ces 2 API : la .NET et la native ; de 2, si vous avez du code CSharp qui fait le taf, c'est que vous avez déjà des wrappers .NET et qu'on se fait chier POUR RIEN !!!
Le code CSharp montre que l'interface .NET INodeMap est assez proche (mais pas identique) à celle correspondant à l'interface INodeMap native.
L'API .NET et bien plus stricte niveau typage car "GetNode" n'est pas utilisé mais "GetNode<IEnum>" qui semble utiliser un type "IEnum" bien plus spécialisé que "INode". C'est du typage "générique" .NET avec coté C++ Natif des choses approchantes comme les templates. Vérifiez coté C++ Natif si vous n'avez pas ces interfaces "templatisées" permettant un bien meilleur typage.
En en revient à POURQUOI VOUS N'UTILISEZ PAS CES PUTAINS D'ASSEMBLIES .NET QUE VOUS AVEZ DÉJÀ ??? (Et qui semble bien mieux que cette API Native toute moisie !!!)
Le code que vous nous donnez UTILISE un INodeMap, nous, il nous faut savoir comment est créé (cf. Design Pattern de création) le paramètre "nodeMap" de la méthode "AcqImg". Mais si vous regardez dans les codes d'exemple en CSharp, vous aurez le Design Pattern de création en .NET, pas celui en C++ Natif.
Donc, essayez de trouver un exemple de création d'une classe dérivant de INodeMap, en C++ Natif, pour voir comment c'est censé être créé. Vous pouvez aussi voir un exemple d'utilisation de la méthode "GetNode", en C++ Natif, et remonter les appels pour revenir au point de création du paramètre "nodeMap" de "GetNode".
>Pour la ligne 26, j'indique d'abord le return avant de décharger ?
???
Vous ne pouvez rien faire après un return.
Il faut s'assurer que gcstring soit le propriétaire (l'ownership des informations, ici les octets contenant les codes ASCIIs de la chaîne de caractère).
Le plus simple, c'est de regarder le code source de l'opérateur d'affectation de la classe gcstring prenant un "const char*" en paramètre et de voir s'il copie la contenu de la chaîne ou pas. (C'est de l'Open Source, c'est fait pour ça.)(Après, il y a la documentation des classes, mais vous semblez un peu trop perdu en C++ pour la comprendre, non ?)
S'il n'est pas propriétaire, la mémoire contenant la chaîne de caractère peut être allouée à autre chose à n'importe quel moment, ce qui potentiellement est catastrophique.
En C++, vous devez maîtriser qui est propriétaire de la mémoire et beaucoup de fonctionnalités récentes du C++ permettent de facilement gérer cela. Mais une classe qui accept directement une affectation depuis un pointeur nu comme un "const char*", c'est que c'est du vieux code casse-gueule et qu'il faut donc vérifier qu'ils ont fait correctement les choses.
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
>Puis-je savoir pourquoi faut-il mettre le "*" du côté du type ?
En C, puis en C++, l'astérisque "*" qui indique l'usage d'un pointeur "nu", chose très déconseillée en C++ depuis la norme C++11 (2011).
Mais cet astérisque doit, lors d'une déclaration/définition de variable, pour ne pas le confondre avec l'opérateur/opération "multiplication", soit être collé à la fin du type de la variable, soit au début du nom de la variable.
Donc, ça c'est OK :
char* c1;
char *c2;
Mais, ça, si ça compile, c'est un UB (Undefined Behaviour)
char * c3;
>je n'ai qu'un exemple d’exécution en c++
Montrez-le nous.
>Je souhaiterais coder cela de manière indépendante.
Indépendante de quoi ???
Pour le code suivant, plus simple, ne fonctionnerait pas ? :
Si "msclr::interop::marshal_as<gcstring>" fait en sorte de copier les données sans une zone "safe", elle le ferait aussi bien, que la variable passée en paramètre soit une variable locale ou une variable "paramètre".
.NET a un système "d'internalisation" des chaînes de caractère qui est très particulier, donc ne pas utiliser des choses qui sont censées fonctionner avec d'autres types et les transposer sur des String^.
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
int AcqImg(CameraPtr pCam, INodeMap& nodeMap, INodeMap& nodeMapTLDevice) //CameraPtr propre au SDK
{
int result = 0;
try
{
CEnumerationPtr ptrAcquisitionMode = nodeMap.GetNode("AcquisitionMode");
if (!IsAvailable(ptrAcquisitionMode) || !IsWritable(ptrAcquisitionMode))
{
cout << "Unable to set acquisition mode to continuous (enum retrieval). Aborting..." << endl << endl;
return -1;
}
...
La compilation plante à cause de la conversion
error C4996: 'msclr::interop::error_reporting_helper<_To_Type,_From_Type,false>::marshal_as': This conversion is not supported by the library or the header file needed for this conversion is not included. Please refer to the documentation on 'How to: Extend the Marshaling Library' for adding your own marshaling method.
1> with
1> [
1> _To_Type=GIC_3_3::gcstring,
1> _From_Type=System::String ^
1> ]
Le code C++ est assez similaire au code C# sans être identique.
C'est assez bizarre que le code C# n'ait besoin que d'un paramètre "INodeMap" et que la version C++ en ait besoin de 2.
Mais comme on n'a pas le corps de la fonction en entier, c'est peut-être normal.
Il faut que vous remontiez l'appel de la fonction "AcqImg" dans le code C++ pour voir comment ses paramètres "nodeMap" et "nodeMapTLDevice" sont construits dans la fonction appelante, ou dans la fonction appelante de la fonction appelante si ils sont eux même des paramètres de la fonction appelante, et ainsi de suite, juste qu'à trouver le code qui "instancie" ces paramètres. (Mais bon, normalement, les Design Pattern de création, ils devraient être dans la documentation du bidule).
Vos wrapper C++/CLI .NET devront utiliser les mêmes Design Pattern de création que ceux trouvés dans le code C++ Natif.
La ligne 7 du code C++ semble montrer que le code C++ Natif n'utilise pas les mécanismes de template directement, mais un mécanisme bien plus spécifique à Windows, les CComPtr.
Si le type "CEnumerationPtr" derive ou contient, directement ou indirectement un CComPtr ( https://docs.microsoft.com/fr-fr/cpp/atl/reference/ccomptr-class?view=msvc-160 ), nous seront fasse à une API COM et donc qu'on s'enquiquine à faire des wrappers .NET alors que les composants COM qui semblent utilisés par le code C++ Natif SONT DIRECTEMENT UTILISABLE EN .NET !!!
Ne nous auriez-vous pas "caché" des informations/exports de Dll ???
Si c'est le cas, ça fait donc plus de 3 mois qu'on tourne en rond pour rien ?
Pouvez-vous nous donner la déclaration de la classe "CEnumerationPtr" ?
A noter que si c'est bien un CComPtr, le Design Pattern de création devrait être très spécifique à ce type de pointeurs.
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Voici le INodeMap.h.