Link to home
Start Free TrialLog in
Avatar of rbradleyinetlp
rbradleyinetlp

asked on

Difficulty using calloc to size/resize data structure arrays for C app

Hello everyone,

I'm creating a MFC DLL which will be called by a C app.  I need to dynamically create/resize structure arrays so Im using calloc/resize/free.  While calloc seems to work fine for simple data types I'm having difficulty working with structure arrays.  Consider the following ex which compiles fine in VS 2K3 C++.


#include "stdafx.h"
#include "malloc.h"

struct Samp {
      char * string1;
      char * string2;
};


extern "C" _declspec(dllexport) int myFunc(Samp * samp)
{
      int    elems = 20; //For example only;My app will size/resize as it needs to.
      samp = (Samp *)calloc(elems, sizeof(Samp)); //Appears to succeed but Samp can not be referenced as an array
      if (samp != NULL)
      {
            for (int i = 0; i < elems; i++0
            {
                  samp[i].string1 = (char *)calloc(100, sizeof(char));
                  samp[i].string2 = (char *)calloc(100, sizeof(char));
                  // string1 and string2 remain undefined

                  //unable to assign values to samp[i].stringx via strcpy()
            }

                                //cleanup
            for (int i = 0; i < elems; i++0
            {
                  free(samp[i].string1);
                  free(samp[i].string2);
            }
            free(samp);
      } else {
            //insufficient memory; error generated

      }
      
}

Avatar of AlexFM
AlexFM

Do you mean that allocated array cannot be referenced by function caller? In this case you need ** parameter:

extern "C" _declspec(dllexport) int myFunc(Samp ** samp)
{
     int    elems = 20;
     *samp = (Samp *)calloc(elems, sizeof(Samp));
     ...
}

Or error is in the function itself? Function interace is not clear - why do you need Samp* parameter?
Avatar of rbradleyinetlp

ASKER

My problem is two fold.
1) samp cannot be referenced as an array after calloc.  
2) samp member variables can not be allocated/used

After the call to calloc, samp is defined; however, the Watch windows shows samp[0] as an "undefined value".  samp.string is also "undefined value"

I receive a null reference exception when I used
extern "C" _declspec(dllexport) int myFunc(Samp ** samp)
{
     int    elems = 20;
     *samp = (Samp *)calloc(elems, sizeof(Samp));
     ...
}
To clarify further, the error is in the function iteslf.  The paramater is needed as an inpu/output parameter between the C application and the C++ DLL.  The C application will create the pointers, my DLL will allocate memory for them and populate with data then return to the calling appl, the calling appl will then manipulate the arrays and free memory.
According to your description, function definition should be:

extern "C" _declspec(dllexport) int myFunc(Samp ** samp)
{
     int    elems = 20;
     *samp = (Samp *)calloc(elems, sizeof(Samp));
     ...
}

Caller:
int* p;
myFunc(&p);
// work with array allocated by myFunc

About bug inside of function, your description is missing details. What does this mean "cannot be referenced"? Do you have exception? What exception exactly, what is code line?
Why do you use calloc and not C++ new? C client doesn't care what is the way of allocation. In any case, you need to write function in Dll which releases this pointer.
I am able to reference the allocated memory as a single structure instead of as an array of structures.

strcpy(samp->string, "hello");     //has no effect but does not generate compile/runtime errors
strcpy(samp[0]->string, "hello"); //index '0' out of bounds

In the code below the first pass works fine.  The second pass generates a Null Ref Exception when I allocate memory for the char*.  If I do not calloc the char * within the structure then I'll get a Null Ref Exception with strcpy.


extern "C" _declspec(dllexport) int myFunc(Samp ** samp)
{
  int elems = 20;      //app will dynamiclaly decide how many elements are necessary
  int calcElems = 0;

  *samp = (Samp *)calloc(elems, sizeof(Samp));

  calcElems = _msize(*samp)/sizeof(Samp);
  for (int index = 0; index < calcElems; index++)
  {
    samp[index]->string1 = (char *)calloc(10, sizeof(char));
      //if index==0 calloc works; if index==1 calloc generates Null Ref Exception
      //after calloc string1 and string2 still shows as "Undefined Value" in Watch window.
    strcpy(samp[index]->string1, "hello");
      //no effect; const "hello" is never copied to string1
  }
}
The third line in my previous post should read ...
strcpy(samp[1]->string1, "hello"); //generates null ref exception not index out of bounds

To answer your prior questions.
The C appl will be responsible for freeing the memory I allocate after it manipulates the data.  To my knowledge C does not support delete and mixing new/free is a no-no.
Avatar of DanRollins
A lot of problems related to memory allocations in MFC DLLs have to do with failure to use

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

in the DLL (at every entry point).  Some related info:
   TN058: MFC Module State Implementation
   http://msdn.microsoft.com/library/en-us/vcmfc98/html/_mfcnotes_tn058.asp

I'm not certain, but I think the problem is more pronounced in Debug builds -- somehow relating to the internal tracking that MFC does in making and verifying allocations (e.g., the replacement of the "new" operator with DEBUG_NEW).

-- Dan
This MSDN article relates to using STL objects in DLLs, but I believe that the memory allocation issues are similar

     You may experience an access violation when you access an STL object through
     a pointer or reference in a different DLL or EXE (originally Q172396)
     http://support.microsoft.com/default.aspx?scid=kb;en-us;172396

=-=-==-=-=-=-
Another thing to try (though I'm not sure why) would be to use new rather than calloc.  It's possible that the new handler will contain special handling that might eliminate the problems.
I am using AFX_MANAGE_STATE( AfxGetStaticModuleState() ); in the exported function of my DLL (not reflected in my sample).  My code for allocating memory is actually in helper functions where data is retrieved from a database, manipulated, stored in the allocated memory so the C appl can then use the data and free memory.

I'm not even progressing beyond the point of allocating memory so right now the problem is local to the routines responsible for allocating memory.  Not in the return to the caller.
Just as a sanity check, I tried this:
#include "stdafx.h"
#include "malloc.h"
#include <string.h>

struct Samp {
      char * string1;
      char * string2;
};

extern "C" _declspec(dllexport) int myFunc(Samp * samp)
{
      int    elems = 20; //For example only;My app will size/resize as it needs to.
      samp = (Samp *)calloc(elems, sizeof(Samp)); //Appears to succeed but Samp can not be referenced as an array
      if (samp != NULL) {
            for (int i = 0; i < elems; i++ ) {
                  samp[i].string1 = (char *)calloc(100, sizeof(char));
                  samp[i].string2 = (char *)calloc(100, sizeof(char));
                  // string1 and string2 DO NOT remain undefined
                  // NOT unable to assign values to samp[i].stringx via strcpy()
                  strcpy( samp[i].string1, "Hi there 1" );
                  strcpy( samp[i].string2, "Hi there 2" );
            }

            //cleanup
            for (int j = 0; j < elems; j++ ) {
                  free(samp[j].string1);  // (see note)
                  free(samp[j].string2);
            }
            free(samp);
      } else {
            //insufficient memory; error generated
      }
      return elems;
}


int main(int argc, char *argv[])
{
      Samp* pSamp= 0;
      myFunc( pSamp );
      return(0);
}

=-=-=-=-=-=-=-=-=-=-=-=-=
It compiles without error, allocates the string data storage, allows assignment of string data to the allocations,  and frees without error.

One thing it does NOT do is return a valid pointer to the allocated data (related to the need to pas a Samp** rather than a Samp* -- as discussed above).  But first things first... your version of this code needs to work before the next step can be taken.

-- Dan
Are you saying you were able to copy strings into the string1 and string2 char*??

I tried this again and after the first pass in the for loop samp[0].string1 is undefined.  If I try to see its value in the Command Window I get the following.  In case something was changed I copied the code you pasted in.  If it is working for you I'm at a loss.

Command Window - Immediate
-----------------------------------------------------------------
? samp[0].string1
error: index '0' out of bound for pointer/array 'samp'
? (Samp *)samp
0x00000000 { string1=<undefined value> }
    string1: <undefined value>
    string2: <undefined value>
? samp
{Samp} { string1=<undefined value> }
    string1: <undefined value>
    string2: <undefined value>
-----------------------------------------------------------------

This is immediately after the first pass through the for loop.
Just for kicks I tried new/delete.  Result is the same.  I also tried calloc/free & new/delete with char arrays (no calls to DLL) and they works fine.

Samp *samp;
samp = (Samp *)new Samp[20];
samp[0].string1 = (char *)new char[10]; // does not generate an exception
strcpy(samp[0].string1, "hello");              // does not generate an exception but also does not copy data

//now I run the following commands in Command Window
//------------------------------------------------------------------------------
//Command Window - Immediate
//------------------------------------------------------------------------------
//? samp
//{Samp} { string1=<undefined value> string2=<undefined value> }
//   string1: <undefined value>
//   string2: <undefined value>
//
//? samp[0].string1
//error: index '0' out of bound for pointer/array 'samp'
//
//? (Samp *)samp
//0x00000000 { string1=<undefined value> string2=<undefined value> }
//    string1: <undefined value>
//    string2: <undefined value>
//------------------------------------------------------------------------------

delete [] samp;
I started from scratch with a new project and it works to some degree.  I think the problem I was having has something to do with .NET.  Originally I'd created a "Console Application .NET" mindless of any differences.  This time I created a "Win32 Console Application".

I'm having trouble figuring out the indirection necessary for the myFunc() call.  It is still bombing out allocating memory for the char * on the second pass.
The one last effort paid off.  The following worked fine in the new non-.NET appl.


#include "stdafx.h"
#include "conio.h"
#include "malloc.h"

struct Samp {
      char * string1;
      char * string2;
};


extern "C" _declspec(dllexport) Samp * myFunc()
{
      int nElems = 0;
      int index = 0;
      Samp *samp;

      samp = (Samp *)calloc(20, sizeof(Samp));

      if (samp)
      {
            nElems = (int)(_msize(samp)/sizeof(Samp));

            //      allocate memory for each char * in the array
            for (index = 0; index < nElems; index ++)
            {
                  //if (index > 0)
                  //      samp[index] = (Samp *)calloc(1, sizeof(Samp));
                  samp[index].string1 = (char *)calloc(10, sizeof(char));
                  samp[index].string2 = (char *)calloc(10, sizeof(char));

                  sprintf(samp[index].string1, "Hello %02d", index);
                  sprintf(samp[index].string2, "%04d", (index + 100));
            }

      }
      return samp;
}


int _tmain(int argc, _TCHAR* argv[])
{
      int nElems = 0;
      int index = 0;

      Samp *samp = 0;
      samp = myFunc();

      nElems = (int)(_msize(samp)/sizeof(Samp));

      //      print values
      for (index = 0; index < nElems; index ++)
      {
            printf(samp[index].string1); printf("\t");
            printf(samp[index].string2); printf("\n");
      }

      //      cleanup
      if (samp)
      {
            nElems = (int)(_msize(samp)/sizeof(Samp));

            for (index = 0; index < nElems; index ++)
            {
                  free(samp[index].string1);
                  free(samp[index].string2);
            }

            free(samp);
      }

      //      exit
      printf("\n\nPress Any Key To Exit ... ");
      getch();

      return 0;
}
My next suggestion was for you to make myFunc() return a Samp* -- just to simplify it (but I see you hit on that yourself :-).

An important error-reducing trick is this one:
              free(samp[index].string1);
              samp[index].string1= 0;   // null the pointer to help detect invalid re-use
              ...
             free(samp);
             samp= 0;       // null the pointer to help detect invalid re-use

It only makes a difference if your program erroneously attempts to write new data into the allocation after it has been freed.  But if the program does make that mistake, you will get an immediate exception -- so it helps you track down the error in seconds rather than hours or days ;-)

-- Dan
Thanks for the suggestion.  Now I'm left struggling with a local heap violation.  I'm seeing a lot of posts hit on this topic but Im not finding the answer I think I need.  I am able to create the pointer local to the exe, allocate memory within the dll, write data to the memory within the dll, return to the exe caller and read the data from allocated memory ... then the free() call fails.  Debug Assertion _CrtIsValidHeapPointer Fails when I attempt to free within the exe.

Since I'm new to experts-exchange I'd also like to ask if it is appropriate that this thread continue or should it be split to another thread?

Thank you both for your input here!
ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you for all your help!  I'll be digging some more later.  For now we've decided to make it work with a static width array............ it works.

I want to assign points and grade but I want to take a stab at some of the suggestions in the other threads first.  I'll do so then finish here.
Hi there,

Concerning the latter question.
Maybe you are mixing release & debug builds for the dll, exe respectively. Then you will get just this CrtIsValidHeapPointer assertion.

Cheers,
Sebastian