Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iterating over memory allocated with Marshal.AllocHGlobal()

Tags:

c#

pinvoke

I have a 3rd party C library which one of its exported method's is as follows:

#define MAX_INDEX 8
int GetStuff(IN char* index[MAX_INDEX], OUT char* buf, IN size_t size);

The first argument is populated with pointers into the buf argument where specific strings are stored. The size indicates how long each string is expected to be in the buf buffer. My C# P/Invoke method for this currently looks like this:

[DllImport("Path/To/Dll", CharSet = CharSet.Ansi)]
private static extern int GetStuff(IntPtr indecies, IntPtr buf, Int32 size);

The C# P/Invoke method is private because I'm wrapping it's functionality in a public "getter" method which handles the allocation/deallocation of memory for the caller. When I use this method in C++, iterating is quite simple actually. I simply do something like:

char* pIndecies[MAX_INDEX];
char* pBuffer = new char[MAX_INDEX * (256 + 1)]; // +1 for terminating NULL

GetStuff(pIndecies, pBuffer, 256);

// iterate over the items
for(int i(0); i < MAX_INDEX; i++) {
    if(pIndecies[i]) {
        std::cout << "String for index: " << i << " " << pIndecies[i] << std::endl;
    }
}

Because of how these are used in C++, I decided I should probably use IntPtr objects and just allocate the memory that I'll need from the heap, call into the native code, and iterate over it the way I would in C++. Then I remembered that chars in C# are unicode characters and not ASCII characters. Would iteration in C# work the same even if I placed the iteration in an unsafe code block? My first thought was to do the following:

IntPtr pIndecies = Marshal.AllocHGlobal(MAX_INDEX * 4); // the size of a 32-pointer
IntPtr pBuffer = Marshal.AllocHGlobal(MAX_INDEX * (256 + 1)); // should be the same
NativeMethods.GetStuff(pIndecies, pBuffer, 256);

unsafe {
    char* pCStrings = (char*)pIndecies.ToPointer();
    for(int i = 0; i < MAX_INDEX; i++) {
        if(pCStrings[i])
            string s = pCStrings[i];
    }
}

My question then is, "How should I iterate over what this native code method returns?" Is this the right way to marshal to this function? Should I use a StringBuilder object for the second argument? One constraining problem is that the first argument is a 1:1 mapping of a structure behind the GetStuff() method. The value of each index is crucial for understanding what you're looking at in the second buffer argument.

I appreciate any suggestions.

Thanks, Andy

like image 330
Andrew Falanga Avatar asked Apr 02 '12 18:04

Andrew Falanga


1 Answers

I think you are on the right track, but I'd do it without using unsafe code. Like this:

[DllImport("Path/To/Dll", CharSet = CharSet.Ansi)]
private static extern int GetStuff(IntPtr[] index, IntPtr buf, Int32 size);
....
IntPtr[] index = new IntPtr[MAX_INDEX];
IntPtr pBuffer = Marshal.AllocHGlobal(MAX_INDEX * 256 + 1);
try
{
    int res = NativeMethods.GetStuff(index, pBuffer, 256);
    // check res for errors?
    foreach (IntPtr item in index)
    {
        if (item != IntPtr.Zero)
            string s = Marshal.PtrToStrAnsi(item);
    }
} 
finally
{
    Marshal.FreeHGlobal(pBuffer);
}

This is a pretty direct translation of your C++ version.

like image 160
David Heffernan Avatar answered Oct 30 '22 07:10

David Heffernan