• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 2572
  • Last Modified:

Marshalling an array of structs

Here's what I have.

C++:
---------
struct A
{
Foo * foo;  // dynamic array of structs
}

struct Foo
{
// blah
// blah
}


C#:
------

struct A
{
Foo [] foo;
}

struct Foo
{
// blah
// blah
}


What is the correct way to Marshall the C# "Foo [] foo" to its C++ equivalent?  I was thinking it's something like this?

[MarshalAs(UnmanagedType.LPStruct)]);
Foo [] foo;


Thanks.
0
DiamonDogX
Asked:
DiamonDogX
  • 19
  • 15
1 Solution
 
vo1dCommented:
custom arrays are not marshalled. you will have to do it on your own.
you can take a look at this thread:
http://www.experts-exchange.com/Programming/Programming_Languages/C_Sharp/Q_21831873.html
in the class:
 [StructLayout(LayoutKind.Sequential)]
          public class SYSTEM_HANDLE_INFORMATION
          {
               public int NumberOfHandles;              
               public IntPtr Handles;
               public SYSTEM_HANDLE_INFORMATION(){}
          }
the Handles pointer is pointing to an array of this struct:
[StructLayout(LayoutKind.Sequential)]
          public struct SYSTEM_HANDLE_TABLE_ENTRY_INFO
          {
               public ushort     UniqueProcessId;
               public ushort     CreatorBackTraceIndex;
               public byte          ObjectTypeIndex;
               public byte          HandleAttributes;
               public ushort     HandleValue;
               public IntPtr     Object;
               public IntPtr     GrantedAccess;
          }

to be able to know, how much structs you got in your array, you will have to save the number of elements in your structA.
0
 
DiamonDogXAuthor Commented:
Let me show you what I'm ultimately wanting to do:

[DllImport("foo.dll")]
public static extern void setIniData(A a);

I need to call the C++ setIniData function and I simply pass in the whole C# struct. (I have this working without the array of structs.  I actually have some other variables in the struct A).

Are you saying my C# version of the struct should be:

struct A
{
IntPtr foo;
}

Where would the actual Foo objects be stored in my C#?  I think in your example link, instead of GetHandleInfos, I want to push my data to my C++ (i.e. managed to unmanaged).  Am I making any sense?
0
 
DiamonDogXAuthor Commented:
Maybe I need to do Marshal.StructureToPtr...
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
vo1dCommented:
yes, thats what you will have to do.but to iterate through your array of A structs you will have to now the number of elements in that array.
could you modify your c++ class in the way, that you could pass a struct like that to your function and it sets the number of elements in the array?
struct A
{
   int     numberOfFoo;
   IntPtr foo;
}

another problem could be, that you will get a corrupted memory error if you are making 'A' as  struct  instead of a class.then you will have to change it like
[StructLayout(LayoutKind.Sequential)]
public class A
{
   public int     numberOfFoo;
   public IntPtr foo;
   public A(){}
}

have you took a look at the link, i posted? it is nearly the same for your problem.

0
 
DiamonDogXAuthor Commented:
In your example you have the statement:
SYSTEM_HANDLE_TABLE_ENTRY_INFO* pCurrentHandle     = ((SYSTEM_HANDLE_TABLE_ENTRY_INFO*)( &pHandleTableInfo));

but that doesn't compile for me (I do a similar statement with Foo * foo = ....).  I get "Error Cannot take the address of, get the size of, or declare a pointer to a managed type."  How can I iterate through?
0
 
vo1dCommented:
you will have to bring your unmanaged struct into managed context like i did with this line:
SYSTEM_HANDLE_INFORMATION handleInfo    = Marshal.PtrToStructure(dataPtr, typeof(SYSTEM_HANDLE_INFORMATION)) as SYSTEM_HANDLE_INFORMATION;
in your example, it would be like this:

A handleInfo    = Marshal.PtrToStructure(dataPtr, typeof(A)) as A;

how does your code look like? could you post something?
0
 
DiamonDogXAuthor Commented:
Sorry vo1d... guess I'm just not quite getting it.  Here's the pertinent code:

C++:
-------------------------------------------------------------------------------------
extern "C" __declspec(dllexport) IniData * getIniData()
{
      return &(App::Instance()->iniData);
}

extern "C" __declspec(dllexport) void setIniData( IniData iniData )
{
      App::Instance()->iniData = iniData;
}

#pragma pack( push, 1 )
typedef struct
{
        DISEnumeration * denyDisEnumList;
      int denyDisEnumListSize;

} IniData;
#pragma pack( pop )

#pragma pack( push, 1 )
typedef struct
{
// blah
// blah
} DISEnumeration;
#pragma pack( pop )



C#:
-----------------------------------------------------------------------------

        [DllImport("TheApp.dll")]
        public static extern IntPtr getIniData();

        [DllImport("TheApp.dll")]
        public static extern void setIniData(IniData iniData);


[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct DisEnum
{
// blah
// blah
}

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public class IniData
{
        public IntPtr denyDisEnumsPtr;
        public int denyDisEnumListSize;

       // I would like to be able to have this:

       public void SetDenyEnumList(DisEnum[] denyDisEnums)
      {
              ????????????????????????
       }
       public unsafe DisEnum[] GetDenyEnumList()
       {
             ????????????????????????
       }
}






0
 
vo1dCommented:
what kind of data is stored in the
DisEnum struct DiamonDogX?
only valuetypes?
0
 
DiamonDogXAuthor Commented:
Yes
0
 
vo1dCommented:
ok, first of all you wil have to extend your c++ functions.
because you will have to allocate an unmanaged memory block in your managed context, which you will pass to your setinitdata function, you need a function which will only return the count of the elements, which will be stored in the array. whith that count, you can allocate the needed memory.
the call yould be then something like this:

IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(INIT_DATA)) + GetDenyElementCount() * Marshal.SizeOf(typeof(DIS_ENUM)));

the structures should look like this:

[StructLayout(LayoutKind.Sequential)]
public struct DIS_ENUM
{
  public // blah
  public // blah
}

[StructLayout(LayoutKind.Sequential)]
public class INI_DATA
{
  public int     DisEnumsCount;
  public IntPtr DisEnums;

  public INI_DATA(){}
}

my question now, is it possible for you to implement the counting function in your c++ dll?
and another question to your set iniData function, what does teh line
App::Instance()->iniData = iniData;
do?
there is no method which fills the initData structure. in that line you are only referencing to that structure but do not fill it.
0
 
DiamonDogXAuthor Commented:
Yes it is possible for me to implement the counting function in the the DLL (I have access to everything).  I basically need to make a C++ function that will allocate the needed memory on that side right?  Then I should be able to pass the whole C# INI_DATA struct using setiniData.  As far as the line "App::Instance()->iniData = iniData;"........ iniData is a member variable of App (IniData iniData;), and that call actually does fill in the struct... I've tested it with simple structs with just value variables and have been able to pass the C# struct that has a bunch of values set in it to the C++ struct using that call and it has worked.  Just need to get it work with the "IntPtr DisEnums", etc.
0
 
vo1dCommented:
i ment making memorallocation on your c# side with such a call:
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(INIT_DATA)) + GetDenyElementCount() * Marshal.SizeOf(typeof(DIS_ENUM)));

but because of your idea, i just got another idea which could be better:
how about modifying your setIniData function on c++ side like this:
IniData* setIniData();

the IniData struct should look like this on c++ side:
typedef struct
{
     DIS_ENUM[] denyDisEnumList;
     int denyDisEnumListCount;

} IniData;
you will make all memory allocation on c++ side and fill that structure on c++ side.

becaucse you are returning a pointer to the filled structure, you would have to implement that function on c# side like this:
[DllImport("TheApp.dll")]
public static extern IntPtr setIniData();

the IniData structure on c# side has to be like this:
[StructLayout(LayoutKind.Sequential)]
public class IniData
{
  public int     DisEnumsCount;
  public IntPtr DisEnums;

  public IniData(){}
}


so, if you have called that method, you could make a call to the marshal class like this:
IntPtr ptr = setIniData(); //<-- your method call
//now get the structure of that pointer
IniData iniData = Marshal.PtrToStructure(ptr, typeof(IniData )) asIniData;

try that and if it works, let me know so that we can go on to the next step, which will be accessing your array.
0
 
DiamonDogXAuthor Commented:
What's the purpose of "IniData* setIniData();" ?  That returns a pointer to the struct, but what does that buy me?  I want all the data in the managed C# struct to go into the unmanaged C++ struct...
0
 
vo1dCommented:
you will get it in the managed context by makign this call:
IniData iniData = Marshal.PtrToStructure(ptr, typeof(IniData)) as IniData;


0
 
DiamonDogXAuthor Commented:
But if I get it in the managed context, then what?  I only need to be able to go one way (from managed to unmanaged).  I just tried something like this:

C++:
----------------------------------
(in App.cpp)
void App::setDenyDisEnumListSize( int size )
{
      iniData.denyDisEnumList = new DISEnumeration[ size ];
      iniData.denyDisEnumListSize = size;
}

extern "C" __declspec(dllexport) void setDenyDisEnumListSize( int size )
{
      App::Instance()->setDenyDisEnumListSize( size );  // calls function above
}


C#:
-------------------------------------

[StructLayout(LayoutKind.Sequential)]
public class INI_DATA
{
  public int     DisEnumsCount;
  public IntPtr DisEnums;

  public INI_DATA(){}

  public void SetDenyEnumList(DisEnum [] d)
 {
   DisEnumsCount = d.Length;
   int bytesToAllocate = Marshal.SizeOf(typeof(DisEnum)) * DisEnumsCount;
   disEnums = Marshal.AllocHGlobal(bytesToAllocate);
   Marshal.StructureToPtr(d, DisEnums, false);
 }

}


When calling "SetDenyEnumList" I get "The specified structure must be blittable or have layout information.  Parameter name: structure."  



0
 
DiamonDogXAuthor Commented:
Am I making sense?  Logically what I want to be doing is constructing and populating the IniData struct on the C# side and call the external function "setIniData( IniData iniData )" that will set the C++ struct with the same data.
0
 
vo1dCommented:
now, that does not make sense.
if you wanna alloc your structure in managed code, you will need information on how much data need to be added to your array so that you can alloc the memory for the structure before you pass it to your function.
it does not make sense to implement a method in your INI_DATA class, if that is what you wanna pass to your c++function.
i only declared it as class instead of a struct because you could get a 'currupted memory exception' after your c++ function has filled that structure and you wanna marshal it back into managed context.
so what you could do is returning the number of bytes which you will have realloc if you call setIniData so that you are able to know, if you need to alloc more memory or not and return 0, if everything went fine.
thats how it works in the example for which i posted the link above.

0
 
DiamonDogXAuthor Commented:
But I call this before I call SetDenyEnumList and setIniData:

void App::setDenyDisEnumListSize( int size )
{
     iniData.denyDisEnumList = new DISEnumeration[ size ];
     iniData.denyDisEnumListSize = size;
}

Would that not allocate the necessary memory on the C++ side?

In your example you have "IntPtr ptr = Marshal.AllocHGlobal(dwSize1);" but ptr now just points to some allocated memory.. it doesn't point to any defined data.  Then you call "GetHandleInfos(ptr);", and to PtrToStructure, etc. but then don't you just have a pointer to some empty struct?
0
 
vo1dCommented:
no, it does not point to de defined data, because the data is beeing stored in that memory with the call:

int result = NtQuerySystemInformation(0x10, ptr, dwSize1, out dwSize2);

which is a kernel function.
the kernel function returns 0 on success, otherwise some error.
i checked for a specific error which could be interpreted that not enough memory was allocated.
so i loop and call that function again until i dont got the specific error code.
thats the explanation to that example.

you create a DISEnumeration array of a specific size on c++ side with the following method, correct.
void App::setDenyDisEnumListSize( int size )
{
     iniData.denyDisEnumList = new DISEnumeration[ size ];
     iniData.denyDisEnumListSize = size;
}
but internally that only means, that you  allocated a block of memory in which 'size *  DISEnumeration' could fit in, nothing else.

but you said a few comments above, you wanna do that allocation on your c# side and just wanna write your data into that allocated memory.
what do you want know? memory allocation on c# side or on c++ side?


0
 
DiamonDogXAuthor Commented:
So you believe the C++ memory allocation I'm doing is correct?  If possible, yes I would like to populate the C# IniData struct and call the external function "setIniData( IniData iniData )", which will write the C# struct to the C++ struct.
0
 
vo1dCommented:
but then it will not work like you do.
you would have to change your setiniData function to:
setIniData(IniData* iniData)
if you wanna allocate the mem in c#.
and you will have to implement that function in c# like this:
[DllImport("TheApp.dll")]
public static extern void setIniData(ĂŽntPtr iniData);

then in your c++ function, fill that structure which you got with that pointer.

is it possible for you, that you can only check on c++ side, how many DISEnumeration objects you will have? i mean, check the number of elements without allocation any memory for the objects?

0
 
DiamonDogXAuthor Commented:
So now I have (in C#):

IniData a = ...... ; // pretend this has been populated

int bytesToAlloc = Marshal.SizeOf(typeof(IniData)) +
                   (a.denyDisEnumListSize * Marshal.SizeOf(typeof(DisEnum)));
IntPtr aPtr = Marshal.AllocHGlobal(bytesToAlloc);
Marshal.StructureToPtr(a, aPtr, false);
setIniData(aPtr);

Does that look about right? (I can't test this at the moment... not at the right computer right now)

What I'm also having trouble with is: if I have a DisEnum [] in C#, how would I convert it to the "IntPtr DisEnums" ?  Similar to above?
0
 
vo1dCommented:
again, you cant pass structures /classes with methods inside from managed into unmanaged code. they are ONLY for dataexchange.
you have to remove any method from the IniData class.
seperate your count method from IniData and make a new function to get the counted elements.
the rest looks fine.
for what do you have to convert DisEnum[] to Intptr?
to retrieve your data later from?
you could do it like this:

pFirst points to the first element of your DisEnum[] array.

fixed (DiseEnum* pFirst = DisEnum)
{
    // Step through the array elements:
    for(int* ptr=pFirst; ptr<pFirst+DisEnum.Length;ptr++)
    {
        *p2; //<-- this is a DisEnum object
    }
}


0
 
vo1dCommented:
sorry, it should look like this:
fixed (DisEnum* pFirst = DisEnum)
{
    // Step through the array elements:
    for(DisEnum* ptr=pFirst; ptr<pFirst+DisEnum.Length;ptr++)
    {
        *ptr; //<-- this is a DisEnum object
    }
}
0
 
DiamonDogXAuthor Commented:
Remember how INI_DATA looked:

[StructLayout(LayoutKind.Sequential)]
public class INI_DATA
{
  public int     DisEnumsCount;
  public IntPtr DisEnums;
}

"IntPtr DisEnums" needs to point to the DisEnums [] array I construct in C#... remember?  How would I do it?  Similar to the way I did the IntPtr with the iniData?
0
 
DiamonDogXAuthor Commented:
This is what I have currently (tells me the structure must be blittable or have layout information):

public static void SetDenyEnumList(IniData iniData, DisEnum[] denyDisEnums)
        {
            iniData.denyDisEnumListSize = denyDisEnums.Length;
            int bytesToAllocate = Marshal.SizeOf(typeof(DisEnum)) * iniData.denyDisEnumListSize;
            iniData.denyDisEnumsPtr = Marshal.AllocHGlobal(bytesToAllocate);
            Marshal.StructureToPtr(denyDisEnums, iniData.denyDisEnumsPtr, false);
        }

Know what I'm doing wrong?  I know it's because I'm passing in a DisEnum[] to the Marshal.StructureToPtr call... but I don't know the correct way to do it......
0
 
DiamonDogXAuthor Commented:
By the way, I will be out of town the next three days and unable to respond...just so you know.  But keep posting any help you have and I will check this page when I get back and hopefully figure this darn thing out!
0
 
DiamonDogXAuthor Commented:
So any idea on how to I can convert a DisEnums [] array to a valid IntPtr?  
0
 
vo1dCommented:
this call is not correct, you can't pass an array to the Marshal.StructureToPtr method:

Marshal.StructureToPtr(denyDisEnums, iniData.denyDisEnumsPtr, false);

you will have to pass the INI_DATA to that method.
again, you need a function with which you are able to get the count of DisEnums elements before you will allocate memory for your INI_DATA structure.
custom arrays of structure can't be marshalled by the system.
please provide me with a method, with which i am able to get an integer of DisEnums elements, so that we are able to make the memory allocation.
0
 
DiamonDogXAuthor Commented:
OK.  So let's say I have the DisEnums [] stored somewhere else in the code and I can get the count of the elements via some function "int getNumDisEnums()".  So at that point I would know how much memory to allocate for my INI_DATA struct.  Good.  But how do I populate the "public IntPtr DisEnums;" member in the INI_DATA struct?
0
 
vo1dCommented:
that could be made like in my example i posted in the link at:
http://www.experts-exchange.com/Programming/Programming_Languages/C_Sharp/Q_21831873.html
in that example, the INI_DATA equivalent would be:

          [StructLayout(LayoutKind.Sequential)]
          public class SYSTEM_HANDLE_INFORMATION
          {
               public int NumberOfHandles;              
               public IntPtr Handles;
               public SYSTEM_HANDLE_INFORMATION(){}
          }

where Handles is your DisEnums.
so to get access to all of your DisEnums, you could do it nearly the same like the code of the GetHandleInfos method of my example.
it should look like this(DIS_ENUM is your other structure from which you built the array):


private static unsafe void GetDIS_ENUMData(IntPtr dataPtr)
{              
   INI_DATA iniData  = Marshal.PtrToStructure(dataPtr, typeof(INI_DATA)) as INI_DATA;
   IntPtr pDisEnum    = iniData.DisEnums;
   DIS_ENUM* pCurrentDisEnum     = ((DIS_ENUM*)( &pDisEnum));
               
   for(int disEnumCount = 0; disEnumCount < pDisEnum.DisEnumsCount; disEnumCount ++)
   {                    
     //do whatever you want with the current disEnum entry                    
      pCurrentDisEnum++;      //<-- increment pointer to get access to next disenum structure of the array              
   }              
}
0
 
DiamonDogXAuthor Commented:
Again, I dont want to get access to the DisEnums.  I want to POPULATE them (i.e. set them).  Vice-versa of what you just posted.  How would I do that?
0
 
vo1dCommented:
just to check out, that i understand, what you want:
you wanna allocate the memory for your structures(including the array size of your enums) on c# side.
then you wanna fill that structures with your method on c++ side.
then you wanna get access to that filled structures on c# side.
am i right or wrong?
0
 
DiamonDogXAuthor Commented:
I want to allocate the memory for my structures AND fill them with data on the C# side.  Then I want to send that data to the C++ side (like we discussed, I will also have to let the C++ side know how much memory to allocate on its end).  The C++ methods will do stuff with the data after it is sent... I dont need to be able to retrieve it from the C++ side after I send it over there.  Understand?
0

Featured Post

How to Use the Help Bell

Need to boost the visibility of your question for solutions? Use the Experts Exchange Help Bell to confirm priority levels and contact subject-matter experts for question attention.  Check out this how-to article for more information.

  • 19
  • 15
Tackle projects and never again get stuck behind a technical roadblock.
Join Now