CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 14 of 14
  1. #1
    Join Date
    May 2002
    Posts
    1,798

    Passing an array of char arrays using plain old data types

    I need to write a wrapper for some classes that make extensive use of STL string vectors in order to build a DLL that will work with varous CRTs. To do that, I need to find a way to input an array of char arrays using plain old data types (int, char, etc..) to simulate a std::vector<string>. While at first this seems like a relatively simple problem, considering how easy it is to accomplish using int, or float, I have been completely stymied when it comes to building and passing char array arrays.

    Using a double array, for example:
    Code:
    void Func1(double * fvec, int n)
    {
    	cout << "Here's the vector in Func1:" << endl;
    	for(int i = 0; i < n; i++) 
    		cout << fvec[i] << endl;
    }
    
    int main()
    {
    	double * pvec = new double[9];
    	pvec[0] = 0.123234;
    	pvec[1] = 0.876765;
    	pvec[2] = 0.776895;
    	pvec[3] = 0.345456;
    	pvec[4] = 0.474474;
    	pvec[5] = 0.995845;
    	pvec[6] = 0.765654;
    	pvec[7] = 0.383338;
    	pvec[8] = 0.098345;
    	for(int i = 0; i < 9; i++) cout << pvec[i] << endl;
    
    	Func1(pvec, 9);		// a vector from simple types
    
    	delete [] pvec;
    
    }
    The same technique works for int, float, double, but not for char **. One technique that does sort of work is as follows:
    Code:
    	typedef char CharArray[100];
    	CharArray * pca = new CharArray[9];
    	strcpy_s(pca[0], 100, "bow");
    	strcpy_s(pca[1], 100, "wow");
    	strcpy_s(pca[2], 100, "now");
    	strcpy_s(pca[3], 100, "cow");
    	strcpy_s(pca[4], 100, "how");
    	strcpy_s(pca[5], 100, "sow");
    	strcpy_s(pca[6], 100, "dow");
    	strcpy_s(pca[7], 100, "yow");
    	strcpy_s(pca[8], 100, "mow");
    But I havn't figured out how to pass such an char array pointer to a functions by value (Note that one cannot reliably pass even simple data types to a DLL function by reference).

    I am simply too embarassed to show all of my failed attempts to accomplish this task. Basically, what needs to be done is:
    1 - build a array of character arrays on the heap
    2 - pass that array by value to a function that can use it
    3 - delete the original array to free the heap

    If someone could help me with this, I would be most appreciative.

    mpliam

  2. #2
    Join Date
    Apr 1999
    Posts
    27,449

    Re: Passing an array of char arrays using plain old data types

    Quote Originally Posted by Mike Pliam View Post
    I have been completely stymied when it comes to building and passing char array arrays.
    A char** is not a two dimensional char array. It is merely a pointer to a char pointer.

    Once you go past one level of indirection (one "star"), the rules change. You cannot convert a two dimensional array into a char** (if you did, the compiler will give you an error that the two types are incompatible). This is not the case with a single "*", where a T[] is convertible to a T* for a type T.

    The only way to pass a char** is to pass a char**. This means you must start out with a char**, build the data from it, and then pass it.

    This code has been posted many times, but this does just that
    Code:
    #include <cstring>
    
    template <class DataType>
    DataType **CreateMatrix(int nRows, int nCols)
    {
        DataType ** pMatrix;
        DataType *pBytes;
        //..
        // Allocate rows
        pMatrix = new DataType *[ nRows ];
    
        // Now allocate the total number of bytes needed to fill matrix
        pBytes = new char [nCols * nRows];
    
        // Now point the row pointers in the right place in the memory pool
        for (int index = 0; index < nRows; ++index )
        {
            pMatrix[index] = pBytes;
            pBytes += nCols;
        }
        return pMatrix;
    }
    //..
    //.. Template function to free
    template <class DataType>
    void FreeMatrix( DataType **pMatrix )
    {
        delete [] pMatrix[0]; // Free pool of data
        delete [] pMatrix;   // Free row pointers
    }
    //..
    //..
    void DoSomething(char** p) { }
    
    int main( )
    {
        char **pca = CreateMatrix<char>(10, 100);
        strcpy(pca[0],  "bow");
        strcpy(pca[1],  "wow");
        strcpy(pca[2],  "now");
        strcpy(pca[3],  "cow");
        strcpy(pca[4],  "how");
        strcpy(pca[5],  "sow");
        strcpy(pca[6],  "dow");
        strcpy(pca[7],  "yow");
        strcpy(pca[8],  "mow");
        DoSomething(pca);
        FreeMatrix(pca);  // remove the memory
    }
    Regards,

    Paul McKenzie

  3. #3
    Join Date
    May 2002
    Posts
    1,798

    Re: Passing an array of char arrays using plain old data types

    Hey, Paul

    Thankyou for your most helpful remarks. I have delayed in answering because I find that the approach that you have suggested is fraught with subtleties that I have needed some time to ponder.

    In short, the method you present does allow input and retreival of a char ** object from a DLL. However, I have encountered a problem with memory leak when trying to build a char ** within the DLL to subsequently export. Short of that problem, the method seems to work quite well.

    I must admit that I have not yet figured out how to use the char ** interface to interact with several extensive classes within the DLL.

    I have provided a small demo program that uses your method for any that might be interested. I think it is simplest to consider all char ** as matrices with the first two elements indicating the number of rows and number of columns respectively, thus avoiding extraneous integer parameters. This technique has much flexibility since column, row and (psuedo) multidimensional arrays can be passed in this manner (provided the char** format is known).

    The demo program TestXDLL retrieves an internal XDLL char** (matrix) and prints it out on the console. Then sets the retrieved char ** as a matrix in XDLL and save it to disk to verify that it really worked. Deleting the char ** from TestDXLL.cpp has no effect on the memory leak and (amazingly) doesnt crash. Obviously, deleting the char ** object from the generating function inside XDLL before returning the char ** object results, as one would anticipate, in nothing returned and a crash.

    Paul, I want to thank you again for your help. Your questions and comments are most welcome.

    Mike
    Attached Files Attached Files
    mpliam

  4. #4
    Join Date
    Oct 2000
    Location
    London, England
    Posts
    4,773

    Re: Passing an array of char arrays using plain old data types

    When you allocate memory inside a DLL you must always deallocate it in the same DLL, i.e. you must have some callback method to do the deletion, and not call delete from the client.

    If it is a class then ensure that the destructor is either virtual or is not implemented in the header file, i.e. not inlined, and this applies often even if the destructor implementation is empty.

  5. #5
    Join Date
    May 2002
    Posts
    1,798

    Re: Passing an array of char arrays using plain old data types

    NMTop40,

    When you allocate memory inside a DLL you must always deallocate it in the same DLL,
    Yes. The demo makes that point, but fails to demonstrate the proper way to handle the export of a char ** object that originates in the DLL. That's because I don't know how to do it.

    It is my understanding that one should not pass a parameter by reference into a DLL using a DLL function. Adhering to this rule eliminates the possibility of the DLL calling process instantiating and deleting the char ** object. Invoking a 'callback' function sounds intriquing, but I have no clue how to implement such a internal DLL function. Keeping the discussion simple for the time, if the exporting function is not a class function (see the posted demo project ), how does one deal with the memory leak.

    I would appreciate anyone who might address this issue with some code example.
    mpliam

  6. #6
    Join Date
    Apr 1999
    Posts
    27,449

    Re: Passing an array of char arrays using plain old data types

    Quote Originally Posted by Mike Pliam View Post
    Obviously, deleting the char ** object from the generating function inside XDLL before returning the char ** object results, as one would anticipate, in nothing returned and a crash.
    Maybe it's all of that code you have that has nothing to do with the allocation/deallocation that is causing the crash. Have you debugged to make sure you're not overwriting memory? If the pointer values after calling "new" are the same ones when you call "delete", then this is a sign you did overwrite memory with all of that other code.

    If you merely just allocated in the DLL, did nothing else but return this allocated memory to the app, and then have the DLL delete the memory, does the crash go away?

    Regards,

    Paul McKenzie

  7. #7
    Join Date
    May 2002
    Posts
    1,798

    Re: Passing an array of char arrays using plain old data types

    Paul,

    The problem as I see it is that it is not possible within DLL to instantiate a new char ** object and both return that object to the calling program and delete the object by calling FreeMatrix(char **); . For example, in the DLL:

    Code:
    template <class DataType>
    DataType **CreateMatrix(int nRows, int nCols)
    {
        DataType ** pMatrix;
        DataType *pBytes;
        //..
        // Allocate rows
        pMatrix = new DataType *[ nRows ];
    
        // Now allocate the total number of bytes needed to fill matrix
        pBytes = new char [nCols * nRows];
    
        // Now point the row pointers in the right place in the memory pool
        for (int index = 0; index < nRows; ++index )
        {
            pMatrix[index] = pBytes;
            pBytes += nCols;
        }
        return pMatrix;
    }
    //..
    //.. Template function to free
    template <class DataType>
    void FreeMatrix( DataType **pMatrix )
    {
        delete [] pMatrix[0]; // Free pool of data
        delete [] pMatrix;   // Free row pointers
    }
    
    char ** ConvertToCharDblPtr(const TNT::Matrix<string> &Ms)
    {
    	long nrows = Ms.num_rows();
    	long ncols = Ms.num_cols();
    	long N = nrows * ncols + 2;  // leave room for row, col numbers and all elements
        char **pca = CreateMatrix<char>(N, 256);
    
    	// initialize
    	for(int i = 0; i < N; i++)
    		for(int j = 0; j < 256; j++)
    			pca[i][j] = 0x00;
    	
    	char buf[256];
    	sprintf_s(buf, 256, "&#37;d", nrows);
        strcpy_s(pca[0], 256, buf);
    	sprintf_s(buf, 256, "%d", ncols);
        strcpy_s(pca[1], 256, buf);
    
    	int k = 2;
    	for(long i = 1; i <= Ms.num_rows(); i++)
    	{
    		for(long j = 1; j <= Ms.num_cols(); j++)
    		{
    			strcpy_s(pca[k], 256, Ms(i, j).c_str());
    			k++;
    		}
    	}
    
    	return pca;
    
    }// ConvertToCharDblPtr(const TNT::Matrix<string> Ms)
    
    
    // This is the ONLY exported function.
    XDLL_API char ** GetMatrixXDLL()
    {
    	TNT::Matrix<string> S(8,4);
    	S(1,1) = "little"; S(1,2) = "willie"; S(1,3) = "went"; S(1,4) = "";
    	S(2,1) = "to"; S(2,2) = "school"; S(2,3) = "tried"; S(2,4) = "to";
    	S(3,1) = "learn"; S(3,2) = "the"; S(3,3) = "golden"; S(3,4) = "rule";
    	S(4,1) = "gave"; S(4,2) = "three"; S(4,3) = "schoolmates"; S(4,4) = "quite";
    	S(5,1) = "a"; S(5,2) = "wack"; S(5,3) = "cause"; S(5,4) = "he";
    	S(6,1) = "really"; S(6,2) = "had"; S(6,3) = "the"; S(6,4) = "nack";
    	S(7,1) = "now"; S(7,2) = "he's"; S(7,3) = "in"; S(7,4) = "the";
    	S(8,1) = "county"; S(8,2) = "jail"; S(8,3) = "no"; S(8,4) = "bail";
    
    	char ** cdp = ConvertToCharDblPtr(S);
        //FreeMatrix(cdp);  // remove the memory  ??  wipes out cdp and crashes
    
    	return cdp;
    }
    The main executable calls as follows:
    Code:
    #include "xdll.h"
    //..
    int main()
    {
    //..
    char ** cdp = GetMatrixXDLL(); 
    //..
    If I understand correctly, there should be no need to delete cdp in main because it is allocated by the DLL. And the CreateMatrix function that allocated the char ** object in the DLL would not be the same function that existed (if it indeed did exist there) in the calling executable. I am fairly certain that the memory leak originates from the DLL function GetMatrix since changing that function return to
    Code:
        FreeMatrix(cdp);  // remove the memory  ??  wipes out cdp and crashes
    
    	//return cdp;
    	return NULL;
    completely eliminates the memory leak. Further, the crash per se is not the problem. It can be eliminated by proper handling of the NULL char ** object in the calling executable. But returning a NULL char ** object makes it useless for manipulation in the calling program.

    So I'm back to the original question which is: How can one instantiate a char ** object using GetMatrix inside a DLL, return the char ** object to the calling function, and clean up the heap memory in the DLL AFTER the char ** object has been exported ?

    I have no doubt that there is some clever method of doing this, but I simply cant figure it out.

    Last edited by Mike Pliam; May 21st, 2009 at 09:30 PM.
    mpliam

  8. #8
    Join Date
    Apr 1999
    Posts
    27,449

    Re: Passing an array of char arrays using plain old data types

    Quote Originally Posted by Mike Pliam View Post
    Paul,

    The problem as I see it is that it is not possible within DLL to instantiate a new char ** object and both return that object to the calling program and delete the object by calling FreeMatrix(char **);
    ???

    Then no DLL or any application that allocates memory would ever work. You allocate memory in module A, you send the address of the memory to module B, are you saying that module A can't clean up the memory if module B does nothing except access this memory?

    So I'm back to the original question which is: How can one instantiate a char ** object using GetMatrix inside a DLL, return the char ** object to the calling function, and clean up the heap memory in the DLL AFTER the char ** object has been exported ?
    I don't quite understand. It is the client that tells the DLL when they've finished with the memory. The DLL doesn't know when the client will be done with it. That's why there should be two exported functions -- one to allocate, another to deallocate. Is this what you mean?
    I have no doubt that there is some clever method of doing this, but I simply cant figure it out.
    There is nothing to figure out. Module A allocates, module A must be able to deallocate that memory, regardless of what module B does to access the memory.

    Regards,

    Paul McKenzie
    Last edited by Paul McKenzie; May 22nd, 2009 at 04:42 AM.

  9. #9
    Join Date
    Apr 1999
    Posts
    27,449

    Re: Passing an array of char arrays using plain old data types

    Code:
    #include "xdll.h"
    //..
    int main()
    {
    //..
    char ** cdp = GetMatrixXDLL(); 
    //..
    // Do something with the memory
    DestroyMatrixDLL(cdp);
    //...
    }
    This is what I would have expected to see. Again, it is the client that tells the DLL when to allocate and when they've finished with the memory. Similarly,
    Code:
    XDLL_API char ** GetMatrixXDLL()
    {
            char **cdp;
            //...
    	return cdp;
    }
    
    XDD_API DestroyMatrixDLL(char**p)
    {
       FreeMatrix(p);
    }
    That is what I would expect to see in the DLL.

    Regards,

    Paul McKenzie

  10. #10
    Join Date
    May 2002
    Posts
    1,798

    Re: Passing an array of char arrays using plain old data types

    Thanks Paul. Your suggestion works beautifully and completely eliminates the memory leak. Mike
    mpliam

  11. #11
    Join Date
    May 2002
    Posts
    1,798

    Re: Passing an array of char arrays using plain old data types

    I hope not to belabor the point, but this seems like a good place to ask two remaining questions that I have with respect to this topic.

    1 - There is a major problem with bookeeping heap memory allocation and deallocation. Is there a better way to do this than having the user try to keep track of each call that required a new allocation in the DLL? In other words, every time the DLL uses CreateMatrix, there needs to be another call from the calling program to FreeMatrix. In a complex series of function calls, this can get to be difficult to keep track of.

    2 - How can a function inside a DLL signal an error to the calling program, other than using the standard i/o? I believe one cannot reliably use try catch mechanism inside a DLL, nor use C standard exception trapping. Is this correct ? I would like to avoid using an MFC DLL, although the DLL will likely come to be used by an MFC interface.
    mpliam

  12. #12
    Join Date
    Oct 2008
    Location
    Singapore
    Posts
    195

    Re: Passing an array of char arrays using plain old data types

    Quote Originally Posted by Mike Pliam View Post
    I hope not to belabor the point, but this seems like a good place to ask two remaining questions that I have with respect to this topic.

    1 - There is a major problem with bookeeping heap memory allocation and deallocation. Is there a better way to do this than having the user try to keep track of each call that required a new allocation in the DLL? In other words, every time the DLL uses CreateMatrix, there needs to be another call from the calling program to FreeMatrix. In a complex series of function calls, this can get to be difficult to keep track of.

    2 - How can a function inside a DLL signal an error to the calling program, other than using the standard i/o? I believe one cannot reliably use try catch mechanism inside a DLL, nor use C standard exception trapping. Is this correct ? I would like to avoid using an MFC DLL, although the DLL will likely come to be used by an MFC interface.
    These are necessary evils of using DLLs I think because there is no guarantee that the DLL and the client code have the same STL version. I think it depends upon the user of the DLL to use automatic resource (like memory) release after its use.

  13. #13
    Join Date
    Apr 1999
    Posts
    27,449

    Re: Passing an array of char arrays using plain old data types

    Quote Originally Posted by Mike Pliam View Post
    1 - There is a major problem with bookeeping heap memory allocation and deallocation. Is there a better way to do this than having the user try to keep track of each call that required a new allocation in the DLL? In other words, every time the DLL uses CreateMatrix, there needs to be another call from the calling program to FreeMatrix. In a complex series of function calls, this can get to be difficult to keep track of.
    Anything in programming gets difficult to keep track of when the program is complex.

    How is your case any different than programs that need to know to close file handles that are open? Or in general, call an "init" function to set up some entity, and then a "de-init" function to destroy the entity? There are so many API's that work this way, whether they are working with memory, file handles, sockets, etc. In any event, let the programmer create a C++ wrapper class if necessary to make things easier. Then calls to "Create" and "Free" are done on construction and destruction of the object.

    Regards,

    Paul McKenzie
    Last edited by Paul McKenzie; May 25th, 2009 at 10:58 PM.

  14. #14
    Join Date
    May 2002
    Posts
    1,798

    Re: Passing an array of char arrays using plain old data types

    The wrapper class sounds like a good idea. I'll try experimenting with it.

    With regard to the second question, the majority of web-based opinion seems to favor not using try-catch inside a dll, mainly because once you've trapped an error, there isnt much you can do about it, so you might be better off letting the program crash. This seems like a rather defeatist attitude to me. What do you think?
    mpliam

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured