Wrapper c++ to c#.Wywołanie metody zwracającej tablice double w c#.

0

hej
Potrzebuje napisać wprapper z c++ do C# .
Napisałam prosty przykład metodę w c++ która zwraca mi tablice i mam problem w wywolaniem jej w c#. Builduje sie ok , ale problem pojawia sie w debugowaniu .. wyskakuje sie mi blad .
**
An unhandled exception of type 'System.Runtime.InteropServices.MarshalDirectiveEx ception' occurred in WrapperTab.exe

Additional information: Nie można zorganizować 'return value': Nieprawidłowa kombinacja typw zarządzanych/niezarządzanych.**

Jeśli to możliwe bardzo prosiłabym o jakąś pomoc ?
Jakąs korektę, uwagi ?
Będę bardzo wdzięczna .

Kod w c++ :

 #include "stdio.h"
#include <iostream>

extern "C"
{

__declspec(dllexport) double* fun(double tab1[30])
{
	double tabWyn[30];
	
	for(int i =0; i<30;i++)
	{
		tabWyn[i]=tab1[i] + 5;
	}

	return tabWyn;
}

}
 

kod z c# :
Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WrapperTab
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Run();
        }

        public static void Run()
        {
        double[] dane = new double[30];

            #region Dane
            dane[0] = 2.3;
            dane[1] = 2.4;
            dane[2] = 4.5;
            dane[3] = 5.5;
            dane[4] = 5.5;
            dane[5] = 6.7;
            dane[6] = 5.6;
            dane[7] = 4.5;
            dane[8] = 5.5;
            dane[9] = 4.4;
            dane[10] = 2.3;
            dane[11] = 2.3;
            dane[12] = 4.5;
            dane[13] = 5.5;
            dane[14] = 5.5;
            dane[15] = 6.7;
            dane[16] = 2.4;
            dane[17] = 4.5;
            dane[18] = 5.5;
            dane[19] = 5.5;
            dane[20] = 6.7;
            dane[21] = 5.6;
            dane[22] = 4.5;
            dane[23] = 5.5;
            dane[24] = 4.4;
            dane[25] = 2.3;
            dane[26] = 2.3;
            dane[27] = 4.5;
            dane[28] = 5.5;
            dane[29] = 5.5;
            #endregion Dane

            double[] wynik = new double[30];
            wynik = UnsafeNativeMethods.fun(dane); // Tutaj  ???????

            for (int i = 0; i < 30; i++)
            {
                MessageBox.Show(wynik[i].ToString());
            }


       }
    }
}

 

Class1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace WrapperTab
{
    internal static class UnsafeNativeMethods
    {
        const string dllLocation = "D:\\TestWrapperTab\\WrapperTab\\WrapperTab\\TestWrapperTab.dll";
        [DllImport(dllLocation)]
        public static extern double[] fun(double[] tab1);
    }
}
1

Kryptofacet?

Zacznijmy od funkcji w C++. Z tego co pamiętam, zwracanie wskaźników do tablic zadeklarowanych na stosie nie jest najlepszym pomysłem - po wyjściu z funkcji, tablica może już sobie nie istnieć. Po drugie, nawet jak zamienimy alokację na tą ze stertą - też będzie problem, bo nie będziemy mieli jak tej pamięci zwolnić. A raczej będzie to baaardzo kłopotliwe i będzie wiązało się z dopisaniem sporej ilości kodu po stronie biblioteki. Najlepiej będzie, jak nasza biblioteka w C++ dostanie pamięć, na której może operować i tyle.
I jeszcze jedno, domyślny calling convention w C++ to cdecl, a w P/Invoke stdcall. Musimy je dopasować. Dajmy na to, że po stronie C++. Także funkcja będzie wyglądać tak:

extern "C" __declspec(dllexport) void __stdcall fun(double tab1[30], double* wynik)

A po stronie C#:

public static extern void fun(double[] argument, IntPtr wynik);

Skoro zadecydowaliśmy, że pamięć dostarczymy, to ją alokujemy:

IntPtr pwynik = Marshal.AllocHGlobal(30 * 8);

Wywołujemy funkcję, zamieniamy nasz wskaźnik na tablicę:

fun(argument, pwynik);
double[] wynik = new double[30];
Marshal.Copy(pwynik, wynik, 0, 30);

I usuwamy zaalokowaną pamięć:

Marshal.FreeHGlobal(pwynik);
2
public static extern void fun(double[] argument, IntPtr wynik);

Dobrze kombinujesz, ale można lepiej:

public static extern void fun(double[] argument, double[] wynik);

Z tego co pamiętam, zwracanie wskaźników do tablic zadeklarowanych na stosie nie jest najlepszym pomysłem - po wyjściu z funkcji, tablica może już sobie nie istnieć

Zgadza się. W dodatku większość sensownych kompilatorów (tzn. wszystkie jakie znam) rzuca w tym przypadku ostrzeżenia.

Po drugie, nawet jak zamienimy alokację na tą ze stertą - też będzie problem, bo nie będziemy mieli jak tej pamięci zwolnić. A raczej będzie to baaardzo kłopotliwe i będzie wiązało się z dopisaniem sporej ilości kodu po stronie biblioteki.

A tam, wystarczy jedna funkcja w bibliotece typu FreeWynik(). Albo zaimportowanie do C# jakiejś funkcji zwalniającej co jest gorszym pomysłem bo destruktory jeśli jakieś istnieją nie zostaną wywołane. Ale ofc przekazywanie pamięci to najlepszy pomysł.

1

O faktycznie, domyślny marshallel nie jest taki głupi ;).
W takim razie całość po stronie C# to będzie:

double[] wynik = new double[30];
fun(argument, wynik);
2

@wiatrak - możesz jeszcze spróbować dodać

[MarshalAs(UnmanagedType.LPArray, SizeConst=30)]

przed double[] wynik w sygnaturce funkcji, bo błąd jest wyraźnie związany z Marshalingiem. Wszystkie poprzednie sugestie aktualne.

0

Dziękuję Wam bardzo za tak wyczerpujące posty : ))
Naprawdę bardzo mi pomogły ;)
Prosty przykładzik śmiga !

Ale teraz jak przeszłam do tego konkretnego kodu ponownie pojawił mi się problem z message :
**An unhandled exception of type 'System.Runtime.InteropServices.MarshalDirectiveException' occurred in WrapperLandmark4.exe

Additional information: Nie można zorganizować 'return value': Nieprawidłowa kombinacja typw zarządzanych/niezarządzanych.**

Jeśli to możliwe jak mógłby ktoś look - nąć na to eksperckim okiem bardzo proszę ?
Będę wdzięczna za jakieś uwagi ;)

W c# Form1.cs :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WrapperLandmark4
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Run();
        }


        public static void Run()
        {
            double[] dane = new double[30];

            #region Dane
            dane[0] = 2.3;
            dane[1] = 2.4;
            dane[2] = 4.5;
            dane[3] = 5.5;
            dane[4] = 5.5;
            dane[5] = 6.7;
            dane[6] = 5.6;
            dane[7] = 4.5;
            dane[8] = 5.5;
            dane[9] = 4.4;
            dane[10] = 2.3;
            dane[11] = 2.3;
            dane[12] = 4.5;
            dane[13] = 5.5;
            dane[14] = 5.5;
            dane[15] = 6.7;
            dane[16] = 2.4;
            dane[17] = 4.5;
            dane[18] = 5.5;
            dane[19] = 5.5;
            dane[20] = 6.7;
            dane[21] = 5.6;
            dane[22] = 4.5;
            dane[23] = 5.5;
            dane[24] = 4.4;
            dane[25] = 2.3;
            dane[26] = 2.3;
            dane[27] = 4.5;
            dane[28] = 5.5;
            dane[29] = 5.5;
            #endregion Dane

            double[] wynik = new double[16];
            IntPtr pWynik = Marshal.AllocHGlobal(16 * 8);
           ** UnsafeNativeMethods.Rejestracja(dane, pWynik);**   // tutaj  ??? 
            Marshal.Copy(pWynik, wynik, 0, 16);
            


            for (int i = 0; i < 16; i++)
            {
                MessageBox.Show(wynik[i].ToString());
            }

            Marshal.FreeHGlobal(pWynik);
        }
    }
}

 

C# Class1.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace WrapperLandmark4
{
    internal static class UnsafeNativeMethods
    {
        const string dllLocation = "D:\\WrapperIGSTK4\\WrapperLandmark4\\WrapperLandmark4\\LandmarkDLL.dll";


        [DllImport(dllLocation)]
        public static extern double[] Rejestracja(double[] dane, IntPtr pWynik);
        
    }
}
 

Natomiast w c++ :

extern "C"
{

	
 ** __declspec(dllexport) void __stdcall Rejestracja(double tab[30], double *pReturn)**    // !!
	 {

igstk::RealTimeClock::Initialize();


// BeginCodeSnippet
  typedef igstk::Object::LoggerType             LoggerType;
  typedef itk::StdStreamLogOutput               LogOutputType;
    
  typedef igstk::Landmark3DRegistration
                            Landmark3DRegistrationType;
  typedef igstk::Landmark3DRegistration::LandmarkPointContainerType
                            LandmarkPointContainerType;
  typedef igstk::Landmark3DRegistration::LandmarkImagePointType 
                            LandmarkImagePointType;
  typedef igstk::Landmark3DRegistration::LandmarkTrackerPointType
                            LandmarkTrackerPointType;
  typedef Landmark3DRegistrationType::TransformType::OutputVectorType 
                            OutputVectorType;
  typedef igstk::Transform  TransformType;
// EndCodeSnippet


// BeginLatex
// 
// Then, the registration component is instantiated as follows:
// 
// EndLatex

// BeginCodeSnippet
  Landmark3DRegistrationType::Pointer landmarkRegister = 
                                        Landmark3DRegistrationType::New();
// EndCodeSnippet
 

// BeginLatex
// 
// Next, the landmark containers that hold the landmark image and tracker 
// coordinates are instantiated:
//
// EndLatex

// BeginCodeSnippet
  LandmarkPointContainerType  imagePointContainer;
  LandmarkPointContainerType  trackerPointContainer;
// EndCodeSnippet


  LandmarkImagePointType      imagePoint;
  LandmarkTrackerPointType    trackerPoint;

// BeginLatex
// 
// Then, error event callback objects are instantiated and added to the observer
// list of the registration component, as follows:
//
// EndLatex

// BeginCodeSnippet
  Landmark3DRegistrationInvalidRequestCallback::Pointer 
                  lrcb = Landmark3DRegistrationInvalidRequestCallback::New();
    
  typedef igstk::InvalidRequestErrorEvent  InvalidRequestEvent;

  landmarkRegister->AddObserver( InvalidRequestEvent(), lrcb );

  Landmark3DRegistrationErrorCallback::Pointer ecb = 
                  Landmark3DRegistrationErrorCallback::New();
  typedef igstk::Landmark3DRegistration::TransformComputationFailureEvent 
                                                     ComputationFailureEvent;
  landmarkRegister->AddObserver( ComputationFailureEvent(), ecb );

// EndCodeSnippet

// BeginLatex
// 
// A logger can then be connected to the registration component for 
// debugging purpose, as follows:
//
// EndLatex


// BeginCodeSnippet 
  LoggerType::Pointer   logger = LoggerType::New();
  LogOutputType::Pointer logOutput = LogOutputType::New();
  logOutput->SetStream( std::cout );
  logger->AddLogOutput( logOutput );
  logger->SetPriorityLevel( LoggerType::DEBUG );
  landmarkRegister->SetLogger( logger );
// EndCodeSnippet
   

// BeginLatex
//
// Next, landmark points are added to the image and tracker containers.  The
// state machine of this registration component is designed so that the image
// and tracker coordinates that correspond to each landmark are added
// consecutively. This scheme prevents the mismatch in landmark correspondence
// that could occur when all landmarks image coordinates are recorded first and
// followed by the tracker coordinates.  This design choice is consistent with
// the ``safety by design'' philosophy of IGSTK. The commands are as follows:
//
//EndLatex 

// BeginCodeSnippet
  // Add entry
  imagePoint[0] = tab[0];
  imagePoint[1] = tab[1];
  imagePoint[2] = tab[2];
  imagePointContainer.push_back(imagePoint);
  landmarkRegister->RequestAddImageLandmarkPoint(imagePoint);

  trackerPoint[0] = tab[3];
  trackerPoint[1] = tab[4];
  trackerPoint[2] = tab[5];
  trackerPointContainer.push_back(trackerPoint);
  landmarkRegister->RequestAddTrackerLandmarkPoint(trackerPoint);

  // Add 1st landmark
  imagePoint[0] = tab[6];
  imagePoint[1] = tab[7];
  imagePoint[2] = tab[8];
  imagePointContainer.push_back(imagePoint);
  landmarkRegister->RequestAddImageLandmarkPoint(imagePoint);
    
  trackerPoint[0] = tab[9];
  trackerPoint[1] = tab[10];
  trackerPoint[2] = tab[11];
  trackerPointContainer.push_back(trackerPoint);
  landmarkRegister->RequestAddTrackerLandmarkPoint(trackerPoint);

  // Add 2nd landmark
  imagePoint[0] = tab[12];
  imagePoint[1] = tab[13];
  imagePoint[2] = tab[14];
  imagePointContainer.push_back(imagePoint);
  landmarkRegister->RequestAddImageLandmarkPoint(imagePoint);

  trackerPoint[0] = tab[15];
  trackerPoint[1] = tab[16];
  trackerPoint[2] = tab[17];
  trackerPointContainer.push_back(trackerPoint);
  landmarkRegister->RequestAddTrackerLandmarkPoint(trackerPoint);

  // EndCodeSnippet

  // BeginLatex
  // 
  // More landmarks can be added for the transform computation.  
  // 
  // EndLatex

  // Add 3n landmark
  imagePoint[0] = tab[18];
  imagePoint[1] = tab[19];
  imagePoint[2] = tab[20];
  imagePointContainer.push_back(imagePoint);
  landmarkRegister->RequestAddImageLandmarkPoint(imagePoint);

  trackerPoint[0] = tab[21];
  trackerPoint[1] = tab[22];
  trackerPoint[2] = tab[23];
  trackerPointContainer.push_back(trackerPoint);
  landmarkRegister->RequestAddTrackerLandmarkPoint(trackerPoint);

  // Add target
  imagePoint[0] = tab[24];
  imagePoint[1] = tab[25];
  imagePoint[2] = tab[26];
  imagePointContainer.push_back(imagePoint);
  landmarkRegister->RequestAddImageLandmarkPoint(imagePoint);

  trackerPoint[0] = tab[27];
  trackerPoint[1] = tab[28];
  trackerPoint[2] = tab[29];
  trackerPointContainer.push_back(trackerPoint);
  landmarkRegister->RequestAddTrackerLandmarkPoint(trackerPoint);

  // BeginLatex
  // 
  // After all landmark coordinates are added, the transform computation is 
  // requested as follows:
  // 
  // EndLatex

  // Calculate transform

  // BeginCodeSnippet
  landmarkRegister->RequestComputeTransform();
  // EndCodeSnippet
     
  typedef itk::VersorRigid3DTransform<double>        VersorRigid3DTransformType;
  typedef VersorRigid3DTransformType::ParametersType ParametersType;




  TransformType  transform;
  ParametersType     parameters(6);

  // BeginLatex
  // 
  // To access the transform parameters, a GetTransform callback is instantiated
  // to observe the transform event, as follows: 
  // 
  // EndLatex

  // BeginCodeSnippet
  Landmark3DRegistrationGetTransformCallback::Pointer lrtcb =
    Landmark3DRegistrationGetTransformCallback::New();

  landmarkRegister->AddObserver( 
    igstk::CoordinateSystemTransformToEvent(), lrtcb );
  //EndCodeSnippet


  // BeginLatex
  //
  // To request that the registration component throw an event loaded with 
  // transform parameters, a \code{RequestGetTransform} function is invoked as 
  // follows:
  // 
  // EndLatex

  // BeginCodeSnippet
  landmarkRegister->RequestGetTransformFromTrackerToImage();
  std::cout<<"  "<<std::endl;
 std::cout << "Transform " << transform << std::endl;

/////////////////////////////////////////////////////////////////
// WYNIK 


 //double pReturn[16];


 if(lrtcb->GetEventReceived()) 
{
 transform = lrtcb->GetTransform();
 vtkMatrix4x4 * t = vtkMatrix4x4::New();;
 transform.ExportTransform( * t );

for ( int i = 0; i < 3; i++ )
{
  for ( int j = 0; j < 3; j++ )
    {
     ** pReturn[4*i+j]  = t->GetElement(i,j);**  // tu wynik ... będzie  tablica 16 doubli 
    }
}

t->Delete();

}


}

	
	

}

 
1

public static extern double[] Rejestracja(double[] dane, IntPtr pWynik);
void, a nie double[]

0

aaa tak nie zauważyłam ;)
Wielkie dzięki ;)
Dziękuje bardzo za pomoc !!!

1 użytkowników online, w tym zalogowanych: 0, gości: 1