Link to home
Start Free TrialLog in
Avatar of janar
janar

asked on

passing linked list from c# to c++ dll

I have a x.lib which was made in c++.
In the vc ++ appln we created linked lists (out of structures) and passed them on to these libraries.

We want to expose the functionality available in this library as a web service.

I started constructing a linked list in C#.

[StructLayoutLayoutKind.Sequential,CharSet=CharSet.Ansi )]
public unsafe struct MarketData
{
     public int AuthorId;
     public string LastName;
     public string FirstName;
     public unsafe MarketData * next;
}

In my web service method I write

unsafe
{
     MarketData MktData ;
     MarketData * mkt2;
     MarketData * mkt1 ;
       // acceess database to load rdrCache
        MktData = new MarketData();
        MktData->x = RdrCache.getInt32(0);
        MktData->next = null;
        Mkt1 = MktData;
        while(RdrCache.read())
        {
          Mkt1->next = new MarketData();
          Mkt1 = Mkt1->next ;
          Mkt1->x = RdrCache.getInt32(0);
          Mkt1->next = null;
        }

}

How can I implement this ?

regards
janar

 
Avatar of AvonWyss
AvonWyss
Flag of Switzerland image

I think you can avoid all of the "unsafe" stuff and just use the marshalling provided by the .NET framework.

Declare the structure like this:

[StructLayoutLayoutKind.Sequential,CharSet=CharSet.Ansi )]
public struct MarketData
{
    public int AuthorId;
    public string LastName;
    public string FirstName;
    public IntPtr Next;
}

And make the call to the DLL so that you receive a IntPtr to the first MarketData in your list. Now, use this code to convert it to a managed struct:

public MarketData GetMarketData(IntPtr pointerToData)
{
    return (MarketData)Marshal.PtrToStructure(pointerToData, typeof(MarketData));
}

I think that this should get you going...
I just noticed that I might have answered the question the for the wrong way of converting the list. My code is for reading the list, but you want to create it, right? In this case, use a similar approach:

Use the same struct as in my provious post, then use Marshal.StructureToPtr to copy the first struct into memory. Assign the IntPtr to the next struct in the list, and call Marshal.StructureToPtr again, etc...

Note that you have to allocate the unmanaged memory on which you operate with StructureToPtr.
Sorry, one more hint: you can allocate and release unmanaged memory using Marshal.AllocHGlobal and Marshal.FreeHGlobal...
Avatar of janar
janar

ASKER

The following is the code I came up with :
MarketData MktData;
RdrCache.Read();  // this will get data from SQLDataReader
     IntPtr buffer = Marshal.AllocHGlobal(Marshal.SizeOf(MktData) ); // this assigns a new block of memory
     Marshal.PtrToStructure(buffer,MktData);
     MktData.AuthorId = RdrCache.GetInt32(0) ;
     MktData.LastName = RdrCache.GetString(1);
     MktData.FirstName = RdrCache.GetString(2);
     Marshal.StructureToPtr(MktData,buffer,false);
     MarketData_Head = buffer;
     MarketData_Temporary = buffer;
     while(RdrCache.Read())
     {
     IntPtr buffer = Marshal.AllocHGlobal(Marshal.SizeOf(MktData) );                         Marshal.PtrToStructure(buffer,MktData);               MktData.AuthorId = RdrCache.GetInt32(0) ;
     MktData.LastName = RdrCache.GetString(1);
     MktData.FirstName = RdrCache.GetString(2);          Marshal.StructureToPtr(MktData,buffer,false);      
        .......
       }

This is the kind of code that I could come up with ? I have not compiled it yet becoz of the following points:
I am still not able to figure out the second parameter for Marshal.PtrToStructure.

how should I declare it

a. MarketData MktData
b. MarketData MktData = new MarketData

The next thing is when should I assign the next pointer for the structure

regards
janar
To create and free the linked list, I would use code like this:

          public IntPtr CreateLinkedList() {
               MarketData md=new MarketData(); // create temporary item
               IntPtr buf=IntPtr.Zero; // last item in the linked list will point to null
               while (RdrCache.Read()) {
                    md.Next=buf; // assign previous buffer of the list
                    md.AuthorId=RdrCache.GetInt32(0); // get data....
                    md.FirstName=RdrCache.GetString(1);
                    md.LastName=RdrCache.GetString(2);
                    buf=Marshal.AllocHGlobal(Marshal.SizeOf(md)); // allocate unmanaged memory
                    Marshal.StructureToPtr(md, buf, false); // copy data to unmanaged memory
               }
               return buf; // return pointer to head of the linked list
          }
         
          public void DestroyLinkedList(IntPtr list) {
               MarketData md=new MarketData(); // create temporary item
               while (list!=IntPtr.Zero) { // linked list not empty?
                    Marshal.PtrToStructure(list, md); // copy head item to mb
                    Marshal.FreeHGlobal(list); // free unmanaged memory
                    list=md.Next; // get the next item from mb
               }
          }

Note: freeing is absolutely required to avoid memory leaks; the memory used by the list is not managed memory and therefore never collected by the garbage collector.
Avatar of janar

ASKER

/*this is what I have in c#*/
     [DllImport("webkrmdl.dll")]
     static extern int FRACalculateOav(ref SecurityDetails secData,MarketData * data);

/*this is what I have in the c++ side*/
extern "C" __declspec(dllexport) int __stdcall   FRACalculateOav(SecurityDetails &secDetails,struct MktData * mktData)

/*these are the errors I am getting*/
c:\inetpub\wwwroot\KRMWbSrv\Service1.asmx.cs(89): Pointers may only be used in an unsafe context
c:\inetpub\wwwroot\KRMWbSrv\Service1.asmx.cs(89): Cannot take the address or size of a variable of a managed type ('KRMWbSrv.Service1.MarketData')
c:\inetpub\wwwroot\KRMWbSrv\Service1.asmx.cs(244): The best overloaded method match for 'KRMWbSrv.Service1.FRACalculateOav(ref KRMWbSrv.Service1.SecurityDetails, KRMWbSrv.Service1.MarketData*)' has some invalid arguments
c:\inetpub\wwwroot\KRMWbSrv\Service1.asmx.cs(244): Argument '2': cannot convert from 'System.IntPtr' to 'KRMWbSrv.Service1.MarketData*'

I am not sure of the signatures ?
regards
janar
Note that we got rid of the pointers by using Marshal and IntPtr's. Therefore, your declaration should be:

    [DllImport("webkrmdl.dll")]
    static extern int FRACalculateOav(ref SecurityDetails secData, IntPtr data);
Avatar of janar

ASKER

ReturnValue = FRACalculateOav(ref SecurityDet,buf);
This line is giving me a Object reference not set to an instance of an object, when run from the client.

Any ideas ?

regards
janar

THE CODE FOLLOWS
[DllImport("webkrmdl.dll")]
     static extern int FRACalculateOav(ref SecurityDetails secData,IntPtr buffer);

[WebMethod()]
public int executeFRACalculateOav()
{
int ReturnValue;
StreamWriter sr = File.CreateText("c:\\newcall.txt");  
// try getting data from the cache
SqlDataReader RdrCache = (SqlDataReader)Context.Cache.Get("KrmAuthors");
if(RdrCache == null)
{
// if data not in cache read the data afresh from the database
string DBConnection = "";
DBObject dataLayer = new DBObject(DBConnection);

RdrCache = dataLayer.RunCommand("Select au_lname,au_fname from authors");
sr.WriteLine ("trying to connect to datalayer");
// add the data to the cache
Context.Cache.Add("KrmAuthors", RdrCache, null, Cache.NoAbsoluteExpiration,Cache.NoSlidingExpiration, CacheItemPriority.High,null);
sr.WriteLine ("finished caching");
}
sr.WriteLine("about to create linked list");
MarketData md=new MarketData(); // create temporary item
IntPtr buf=IntPtr.Zero; // last item in the linked list will point to null
while (RdrCache.Read())
{
md.Next=buf; // assign previous buffer of the list
md.FirstName=RdrCache.GetString(0);
md.LastName=RdrCache.GetString(1);
buf=Marshal.AllocHGlobal(Marshal.SizeOf(md)); // allocate unmanaged memory
Marshal.StructureToPtr(md, buf, false); // copy data to unmanaged memory
}

// temporarily set the new security details
sr.WriteLine ("instantiating security data structure");
SecurityDetails SecurityDet = new SecurityDetails() ;
SecurityDet.Cusip = "CMO1";
SecurityDet.PrincipalAmount = 10000.123;
sr.WriteLine("About to call dll");
ReturnValue = FRACalculateOav(ref SecurityDet,buf);
sr.WriteLine("finished calling dll");
sr.Close();
//return buf; // return pointer to head of the linked list
return ReturnValue;
}
Avatar of janar

ASKER

Using the code below I was able to create a linked list that can be passed to c++ dll.

Still I am getting the last item repeatedly ? I am working on this.

Is there a better way of doing this ?

MarketData md=new MarketData(); // create temporary item
IntPtr buf1=IntPtr.Zero;
IntPtr buf2=IntPtr.Zero;
if (RdrCache.Read())
{
buf1=Marshal.AllocHGlobal(Marshal.SizeOf(md)); // allocate unmanaged memory
buf2=Marshal.AllocHGlobal(Marshal.SizeOf(md)); // allocate unmanaged memory
md.FirstName=RdrCache.GetString(0);
md.LastName=RdrCache.GetString(1);
sr.Write(md.FirstName.ToString());
sr.WriteLine(md.LastName.ToString());
md.Next = buf2;
Marshal.StructureToPtr(md, buf1, false); // copy data to unmanaged memory
MarketData_Head = buf1;
buf1 = buf2;
}

while (RdrCache.Read())
{
buf2=Marshal.AllocHGlobal(Marshal.SizeOf(md)); // allocate unmanaged memory
md.FirstName=RdrCache.GetString(0);
md.LastName=RdrCache.GetString(1);
sr.Write(md.FirstName.ToString());
sr.WriteLine(md.LastName.ToString());
md.Next = buf2;
Marshal.StructureToPtr(md, buf1, false); // copy data to unmanaged memory
buf1 = buf2;
}
/*this is because we are creating two memory locations at the same time
  At the end of the recordset the last node pointer will not be having
  a null value instead will be pointing to a valid memory address.The
  next three lines will set the last node pointers value to null*/
buf2 = IntPtr.Zero;
md.Next = buf2;
Marshal.StructureToPtr(md, buf1, false);

regards
janar
Janar, your code creates a memory leak. Also, it is much more complicated than necessary... why aren't you using my code? Probably because it returns the list in reversed order, right? But that should not be an issue since you can have your SQL command reverse the order of your list.

You have the last item twice since you have a note "too much" lying around. In fact, you would have to do the call to Read() before that and depending on the result put IntPtr.Zero into the md structure before the call to StructToPtr(). This code should do what you expect:

               if (!RdrCache.Read())
                    return IntPtr.Zero; // return null if 0 items in list
               MarketData md=new MarketData(); // create temporary item
               IntPtr head=Marshal.AllocHGlobal(Marshal.SizeOf(md)); // allocate unmanaged memory
               IntPtr next=head; // next buffer is the same as the head
               do {
                    md.FirstName=RdrCache.GetString(0); // fill in data bafore Read() is called
                    md.LastName=RdrCache.GetString(1);
                    IntPtr current=next; // store current pointer for marshaling call
                    next=RdrCache.Read() ? Marshal.AllocHGlobal(Marshal.SizeOf(md)) : IntPtr.Zero; // allocate next if any data is available
                    md.Next=next; // set next member in structure (will be null id Read() returned false)
                    Marshal.StructureToPtr(md, current, false); // copy data to current unmanaged memory block
               }     while (buf!=IntPtr.Zero); // if no memory was allocated, the list has reached its end
               return head; // this is the beginning of the list

And, *please* do indent your code. It would make it much easier for me and other people to understand your code!
Oops. Mistyped a line...:

              }     while (buf!=IntPtr.Zero); // if no memory was allocated, the list has reached its end

should read

              }     while (next!=IntPtr.Zero); // if no memory was allocated, the list has reached its end
ASKER CERTIFIED SOLUTION
Avatar of AvonWyss
AvonWyss
Flag of Switzerland 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
Avatar of janar

ASKER

hi,

Thanks for all the help. I have got my code working now.

Indenting -- when I copied over into this area from my actual code the indenting was not okay. That is the reason I had to remove all the indents and paste the code here.

I could not use your exact code becoz in the actual implementation the circumstances are different.

Thanks for the execellent help.

regards
janar
Janar, thank you for the feedback! I'm happy to hear that you could use my suggestions to write your own working code.

Since you seem to be quite new to EE (with only few questions graded so far), please let me suggest that when you receive extensive help from an expert on a question, it would be a nice move of you to raise the points to what you feel appropriate before grading the question in order to show your gratitude to the expert.

Of course, this is not a requirement, but it helps to motivate the experts to deliver more than just the bare response to the question since points are the only reward we are getting.