-2

I have a simple C# GUI which has a button on it. When clicking on the button, a C++ DLL is called and some computations are done inside C++ DLL.

The C# GUI (executable) imports a C++ DLL like this:

class Call_Link_DLL
    {
        [DllImport(@"Link_DLL.dll", EntryPoint = "OPS_Link_LoadFDD")]
        public static extern System.IntPtr OPS_Link_LoadFDD([In,Out,MarshalAs(UnmanagedType.LPStr)] string char_Ptr_octave_bin_dir);
    }

SystemIntPtr_ReturnedByDLL = Call_Link_DLL.OPS_Link_LoadFDD(char_Ptr_octave_bin_dir);

Inside C++ DLL I'm allocating a pointer to a class object like this:

FDD*FDD_Ptr_Object=new FDD();

When C++ DLL is done with its calculations, the pointer is de-allocated like this:

delete FDD_Ptr_Object;//de-allocating makes C# executable (GUI) not able to re-execute without having to close GUI!

But the problem is that: when I click on the GUI button for the 1st time, C++ DLL runs and generates results. But for the 2nd time when I click on the GUI button, the program crashes. I have to close GUI and click on GUI button again to re-execute. The confusing point is that when I comment out the de-allocation line (delete FDD_Ptr_Object;), the GUI works perfect when I push the GUI button for the 2nd time, 3rd time and so on. I was wondering if anybody ran into such a problem. Thanks.

EDIT: I post all the details here:

The following is a simple GUI developed with C#. It has one button, when clicking on the button a C++ DLL (named Link_DLL.dll) would be called.

C# CUI

The following is the code in C# for importing a C++ DLL named Link_DLL.dll:

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

using System.Runtime.InteropServices;

namespace GUI_Call_Link_DLL
{
    class Call_Link_DLL
    {
        [DllImport(@"Link_DLL.dll", EntryPoint = "OPS_Link_LoadFDD")]
        public static extern System.IntPtr OPS_Link_LoadFDD([In,Out,MarshalAs(UnmanagedType.LPStr)] string char_Ptr_octave_bin_dir);
        [DllImport(@"Link_DLL.dll", EntryPoint = "OPS_Link_RunFDD_getFreqDomainResults")]
        public static extern System.IntPtr OPS_Link_RunFDD_getFreqDomainResults([In, Out, MarshalAs(UnmanagedType.LPStr)] string char_Address, [In, Out, MarshalAs(UnmanagedType.I4)]int int_NumChann, [In, Out, MarshalAs(UnmanagedType.I4)]int int_SamplingFreq);
        [DllImport(@"Link_DLL.dll", EntryPoint = "OPS_Link_feedFDD_setNaturalFreqSelectedByUser")]
        public static extern System.IntPtr OPS_Link_feedFDD_setNaturalFreqSelectedByUser([In, Out, MarshalAs(UnmanagedType.LPStr)]string char_Address_File_naturalFreqSelectedByUser);
        [DllImport(@"Link_DLL.dll", EntryPoint = "OPS_Link_RunFDD_getModeShapesResults")]
        public static extern System.IntPtr OPS_Link_RunFDD_getModeShapesResults();
        [DllImport(@"Link_DLL.dll", EntryPoint = "OPS_Link_UnloadFDD")]
        public static extern System.IntPtr OPS_Link_UnloadFDD();
    }
}

The code inside C# to call Link_DLL.dll when button is clicked:

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 GUI_Call_Link_DLL
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //you can copy "Octave-3.6.1" folder and subfolders anywhere you want and give binary path here:
            string char_Ptr_octave_bin_dir = @"C:\Software\Octave-3.6.1\bin\";

            string char_Address = openFileDialog1.FileName;
            int int_NumChann = int.Parse(textBox1.Text);
            int int_SamplingFreq = int.Parse(textBox2.Text);

            string char_Address_File_naturalFreqSelectedByUser = "FDD_TEMP_naturalFreqSelectedByUser.txt";

            System.IntPtr SystemIntPtr_ReturnedByDLL;//char* returned by DLL
            string string_ReturnedByDLL = "Good";//convert to string the char* returned by DLL

            try
            {
                SystemIntPtr_ReturnedByDLL = Call_Link_DLL.OPS_Link_LoadFDD(char_Ptr_octave_bin_dir);
                string_ReturnedByDLL = Marshal.PtrToStringAnsi(SystemIntPtr_ReturnedByDLL);
                if (string_ReturnedByDLL != "Good") { throw new Exception(); }

                SystemIntPtr_ReturnedByDLL = Call_Link_DLL.OPS_Link_RunFDD_getFreqDomainResults(char_Address, int_NumChann, int_SamplingFreq);
                string_ReturnedByDLL = Marshal.PtrToStringAnsi(SystemIntPtr_ReturnedByDLL);
                if (string_ReturnedByDLL != "Good") { throw new Exception(); }

                SystemIntPtr_ReturnedByDLL = Call_Link_DLL.OPS_Link_feedFDD_setNaturalFreqSelectedByUser(char_Address_File_naturalFreqSelectedByUser);
                string_ReturnedByDLL = Marshal.PtrToStringAnsi(SystemIntPtr_ReturnedByDLL);
                if (string_ReturnedByDLL != "Good") { throw new Exception(); }

                SystemIntPtr_ReturnedByDLL = Call_Link_DLL.OPS_Link_RunFDD_getModeShapesResults();
                string_ReturnedByDLL = Marshal.PtrToStringAnsi(SystemIntPtr_ReturnedByDLL);
                if (string_ReturnedByDLL != "Good") { throw new Exception(); }

                SystemIntPtr_ReturnedByDLL = Call_Link_DLL.OPS_Link_UnloadFDD();
                string_ReturnedByDLL = Marshal.PtrToStringAnsi(SystemIntPtr_ReturnedByDLL);
                if (string_ReturnedByDLL != "Good") { throw new Exception(); }
            }
            catch (Exception) { MessageBox.Show(string_ReturnedByDLL); }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //show dialog and get result
            DialogResult DialogResult_Object = openFileDialog1.ShowDialog();
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }

        private void textBox2_TextChanged(object sender, EventArgs e)
        {

        }

        private void textBox3_TextChanged(object sender, EventArgs e)
        {

        }
    }
}

The C++ code in Link_DLL.dll is this:

#include "Link.h"

//Declare handle to FDD_DLL
//This handle is declared within namespace to be accessible from within any function 
namespace namespace_top_of_Link{
    HINSTANCE hDLL=NULL;
}

//Exported function to be called by executable (C# or C++) 
//To load FDD_DLL
//To add dependencies directories to search path
//Dependencies path is determined by executable
extern "C" __declspec(dllexport)
char* OPS_Link_LoadFDD(char* char_Ptr_octave_bin_dir){
    try{
        std::string string_octave_bin_dir = std::string(char_Ptr_octave_bin_dir);//convert char* to std::string
        std::wstring wstring_octave_bin_dir = std::wstring(string_octave_bin_dir.begin(), string_octave_bin_dir.end());// convert std::string to std::wstring
        SetDllDirectory(wstring_octave_bin_dir.c_str());
        //SetDllDirectory((LPCWSTR)L"C:\\Software\\Octave-3.6.1\\bin\\");
        namespace_top_of_Link::hDLL=LoadLibrary((LPCWSTR)L"FDD_DLL.dll");
        if(namespace_top_of_Link::hDLL==NULL){
            throw "FDD DLL or dependencies could not be loaded";
        }
    }
    catch(char*char_Ptr_Exception){
        return char_Ptr_Exception;
    }
    return "Good";
}

//Exported function to be called by executable (C# or C++)
//To run FDD_DLL
//FDD_DLL returns results inside text files
//Results are frequencies and corresponding singular values
extern "C" __declspec(dllexport)
char* OPS_Link_RunFDD_getFreqDomainResults(char* char_Address,int int_NumChann,int int_SamplingFreq){
    try{
        typedef void (*Ptr_OPS_FDD_getFreqDomainResults)(char*, int, int);
        Ptr_OPS_FDD_getFreqDomainResults Ptr_OPS_FDD_getFreqDomainResults_Object=
            (Ptr_OPS_FDD_getFreqDomainResults)GetProcAddress(namespace_top_of_Link::hDLL,"OPS_FDD_getFreqDomainResults");
        Ptr_OPS_FDD_getFreqDomainResults_Object(char_Address, int_NumChann, int_SamplingFreq);
    }
    catch(char*char_Ptr_Exception){
        return char_Ptr_Exception;
    }
    return "Good";
}

//Exported function to be called by executable (C# or C++)
//To pass user-selected natural frequencies to FDD_DLL
//User-selected natural frequencies should be inside a text file
//Address of text file is passed to FDD_DLL
extern "C" __declspec(dllexport)
char* OPS_Link_feedFDD_setNaturalFreqSelectedByUser(const char* char_Address_File_naturalFreqSelectedByUser){
    try{
        typedef void (*Ptr_OPS_FDD_setNaturalFreqSelectedByUser)(const char*);
        Ptr_OPS_FDD_setNaturalFreqSelectedByUser Ptr_OPS_FDD_setNaturalFreqSelectedByUser_Object;
        Ptr_OPS_FDD_setNaturalFreqSelectedByUser_Object=
            (Ptr_OPS_FDD_setNaturalFreqSelectedByUser)GetProcAddress(namespace_top_of_Link::hDLL,"OPS_FDD_setNaturalFreqSelectedByUser");
        Ptr_OPS_FDD_setNaturalFreqSelectedByUser_Object(char_Address_File_naturalFreqSelectedByUser);
    }
    catch(char*char_Ptr_Exception){
        return char_Ptr_Exception;
    }
    return "Good";
}

//Exported function to be called by executable (C# or C++)
//To run FDD_DLL
//FDD_DLL returns results inside text files
//Results are mode shapes at user-selected natural frequencies
extern "C" __declspec(dllexport)
char* OPS_Link_RunFDD_getModeShapesResults(){
    try{
        typedef void (*Ptr_OPS_FDD_getModeShapesResults)();
        Ptr_OPS_FDD_getModeShapesResults Ptr_OPS_FDD_getModeShapesResults_Object=
            (Ptr_OPS_FDD_getModeShapesResults)GetProcAddress(namespace_top_of_Link::hDLL,"OPS_FDD_getModeShapesResults");
        Ptr_OPS_FDD_getModeShapesResults_Object();
    }
    catch(char* char_Ptr_Exception){
        return char_Ptr_Exception;
    }
    return "Good";
}

//Exported function to be called by executable (C# or C++)
//To unload FDD_DLL
extern "C" __declspec(dllexport)
char* OPS_Link_UnloadFDD(){
    try{
        if(FreeLibrary(namespace_top_of_Link::hDLL)==0){//If function can not free, return value is zero
            throw "FDD DLL could not be unloaded";
        }
    }
    catch(char* char_Ptr_Exception){
        return char_Ptr_Exception;
    }
    return "Good";
}

Actually Link_DLL.dll is loading another C++ DLL named FDD_DLL.dll, the C++ code in FDD_DLL.dll is this:

#include "FDD.h"

//Declare object of FDD class
//FDD object is declared within namespace to be accessible from within any function
//FDD object pointer will be de-allocated (delete) when done with it
namespace namespace_top_of_FDD{
    FDD*FDD_Ptr_Object=new FDD();
}

//Exported function to be called by Link_DLL
//To initialize FDD object
//To run FDD object methods
//FDD object methods will return results inside text file
//Results are frequencies and corresponding singular values
extern "C" __declspec(dllexport)
void OPS_FDD_getFreqDomainResults(char* char_Address,int int_NumChann,int int_SamplingFreq){
    namespace_top_of_FDD::FDD_Ptr_Object->m_char_Address=char_Address;
    namespace_top_of_FDD::FDD_Ptr_Object->m_int_NumChann=int_NumChann;
    namespace_top_of_FDD::FDD_Ptr_Object->m_int_SamplingFreq=int_SamplingFreq;
    namespace_top_of_FDD::FDD_Ptr_Object->getMatrixOfChannels();
    namespace_top_of_FDD::FDD_Ptr_Object->getCPSD();
    namespace_top_of_FDD::FDD_Ptr_Object->getSVD();
    namespace_top_of_FDD::FDD_Ptr_Object->getFreqDomainResultsPrinted();
    namespace_top_of_FDD::FDD_Ptr_Object->getFreqPlot();
}

//Exported function to be called by Link_DLL
//To pass user-selected natural frequencies from Link_DLL to FDD_DLL
//User-selected natural frequencies should be inside a text file
//Address of text file is passed
//FDD_DLL would read text file
extern "C" __declspec(dllexport)
void OPS_FDD_setNaturalFreqSelectedByUser(const char* char_Address_File_naturalFreqSelectedByUser){
    namespace_top_of_FDD::FDD_Ptr_Object->getNaturalFreqValue(char_Address_File_naturalFreqSelectedByUser);
}

//Exported function to be called by Link_DLL
//To run FDD object methods
//FDD object methods will return results inside text files
//Results are mode shapes at user-selected natural frequencies
//Finally FDD object pointer will be de-allocated (delete)
extern "C" __declspec(dllexport)
void OPS_FDD_getModeShapesResults(){
    namespace_top_of_FDD::FDD_Ptr_Object->get_NaturalFreqIndex_from_NaturalFreqValue();
    namespace_top_of_FDD::FDD_Ptr_Object->getRealModesFromComplexModesAtNaturalFreq();
    namespace_top_of_FDD::FDD_Ptr_Object->getModeShapesNormalized();
    namespace_top_of_FDD::FDD_Ptr_Object->getModeShapesPrinted();
    //namespace_top_of_FDD::FDD_Ptr_Object->getModesPlot();
    namespace_top_of_FDD::FDD_Ptr_Object->getNormalizedModesPlot();
    //namespace_top_of_FDD::FDD_Ptr_Object->getNormalizedModesPlotAtSinglePlot();
    namespace_top_of_FDD::FDD_Ptr_Object->~FDD();
    //delete namespace_top_of_FDD::FDD_Ptr_Object;//de-allocating makes C# executable (GUI) not able to re-execute without having to close GUI!
    //namespace_top_of_FDD::FDD_Ptr_Object=NULL;
}

FDD::FDD()
        :m_char_Address(""),m_int_NumChann(0),m_int_SamplingFreq(0)
    {
    }

FDD::~FDD(){
}

// The rest of the C++ code inside FDD_DLL.dll is just implementation of methods of FDD class...

As can be seen, there is this line "delete namespace_top_of_FDD::FDD_Ptr_Object;" in FDD_DLL.dll, when I comment out that line, the button of C# GUI can be clicked several times, but when I un-comment that line, the button of C# GUI works when clicked for the 1st time, but for the 2nd time the program crashes.. Please let me know if you have any idea...

Megidd
  • 7,089
  • 6
  • 65
  • 142
  • 1
    Could it be that you do `delete` within `OPS_Link_LoadFDD`? Or when does `delete` happen? – AlexD Aug 06 '14 at 17:18
  • @AlexD Delete happens at the last line of C++ DLL. – Megidd Aug 06 '14 at 17:28
  • 1
    _"Delete happens at the last line of C++ DLL"_ Do you mean that you delete the object just before returning it? – AlexD Aug 06 '14 at 17:31
  • @AlexD Actually C++ DLL doesn't return anything, I just constructs an object, the object carries out the calculations and generates some text files. – Megidd Aug 06 '14 at 17:34
  • 1
    Could you post source code of `OPS_Link_LoadFDD`? – AlexD Aug 06 '14 at 17:39
  • @AlexD I'm going to post all the code now. – Megidd Aug 06 '14 at 17:41
  • @AlexD I posted all the relevant codes. – Megidd Aug 06 '14 at 18:06
  • 1
    No, you posted all your code! Please reduce it to a small working example to reproduce your problem... – Adriano Repetti Aug 06 '14 at 18:09
  • @AdrianoRepetti I'm going to make a small example. – Megidd Aug 06 '14 at 18:11
  • 2
    @user3853917 It seems that `FDD_Ptr_Object` gets created once, when DLL gets loaded, and deleted on _every call_ to `OPS_FDD_getModeShapesResults`. Could it be the reason? BTW, why do you call dtor explicitly: `namespace_top_of_FDD::FDD_Ptr_Object->~FDD();`? – AlexD Aug 06 '14 at 18:14
  • @AlexD For calling destructor explicitly, I have no reason, I just was playing around with the code to figure out this issue. As you pointed out the reason might be the fact that de-allocation is happening with every call... I'm going to work on it... – Megidd Aug 06 '14 at 18:20
  • @AlexD You were right, the problem was the explicit call to object destructor. I commented out the explicit call to destructor, and the de-allocation works without any problem. If you create an answer, I can mark it as solved... – Megidd Aug 06 '14 at 18:53
  • 1
    @user3853917 Ah, good that you posted the answer yourself. Two remarks, besides double destruction. (1) `new` and `delete` still look strangely "asymmetric"; (2) the statement _"when I don't use delete command inside C++ DLL, the CLR would take care of that"_ is wrong; the garbage collector has no idea about memory allocation in unmanaged code. Good luck :)! – AlexD Aug 06 '14 at 20:48
  • @AlexD Thanks, I deleted the wrong sentence about CLR and garbage collector. By the way, I'm not using the `delete` command any more. So now, I don't have any explicit destructor and also I don't have any de-allocation. Now I allow the object to be destructed automatically every time the DLL is freed (unloaded). I feel like it is better not to mess around with `delete` and destructor...The DLL would be freed (unloaded), therefore there might be no memory leak... – Megidd Aug 07 '14 at 14:44
  • 1
    @user3853917 In general, if you call `new`, you should call `delete`. Not sure if it fits to your case, but if lifetime of FDD_Ptr_Object is the same as lifetime of DLL, you may consider using just `FDD FDD_Ptr_Object`, without allocating it with `new`. See http://stackoverflow.com/questions/75701/what-happens-to-global-variables-declared-in-a-dll. – AlexD Aug 07 '14 at 17:15
  • @AlexD Thanks. I implemented your comment in the code and the program works well. I replaced this: `FDD*FDD_Ptr_Object=new FDD();` with these: `FDD FDD_Object=FDD(); FDD*FDD_Ptr_Object=&FDD_Object;` – Megidd Aug 07 '14 at 19:28

1 Answers1

0

The problem is solved now:

As AlexD mentioned, the problem was double destruction of an object. I had these statements in my code:

namespace_top_of_FDD::FDD_Ptr_Object->~FDD();
    delete namespace_top_of_FDD::FDD_Ptr_Object;
    namespace_top_of_FDD::FDD_Ptr_Object=NULL;

The problem was that, explicit call to destructor with namespace_top_of_FDD::FDD_Ptr_Object->~FDD(); and de-allocation with delete namespace_top_of_FDD::FDD_Ptr_Object; were conflicting with each other. The explicit call to object destructor is a bad idea in many cases. The solution was to remove the explicit call to object destructor, so now I just need to de-allocate:

delete namespace_top_of_FDD::FDD_Ptr_Object;
    namespace_top_of_FDD::FDD_Ptr_Object=NULL;

Now the code works perfect.

Megidd
  • 7,089
  • 6
  • 65
  • 142