简体   繁体   中英

Interop C# to C++ struct

I am trying to call some legacy C code using interop in C#. I am not too famliar with how interop works on C# yet but has to work with some confusing structs. I got part of it working but the address messes up when I try to get the struct into the C layer.

I am trying to pass a struct to C code, it will do something to it, and I need to get a result back

I have these structs in C#

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct RETURNBUFFER
    public IntPtr records; //this is the struct RECORD
    public IntPtr infoA; // this is the struct INFO
    public IntPtr infoB; 
    public int number;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct INFO
{
    public IntPtr doc; //this is a handle in C code
    public int cpFirst; 
    public int cpLim; 
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct RECORD
{
    public int size;
}

Records is actually a pointer to another Struct STATS defined in C# like this,

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STATS 
{
    public int size;
    public int a;
    public int b;
    public int c;
    public int d;
    public int e;
    public int f;
    public int g;
}

in the C# layer, i create the struct like the following

        RETURNBUFFER returnBuffer = new RETURNBUFFER();
        returnBuffer.infoA = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(INFO)));
        returnBuffer.infoB = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(INFO)));
        returnBuffer.records = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(STATS)));

When I run my code, I was only able to retrieve the first item in returnBuffer which is returnBuffer.records, all the other item including the int value in returnBuffer is messed up.

I try to debug through it and look into the address value, I found that when the code codes from C# -> C the address is shifted

I am not sure why the address is off,

Here is an example of what happened under a 64bit environment

C#
&ReturnBuffer
0x00000000046f05f8
&ReturnBuffer.records
0x00000000046f05f8
&ReturnBuffer.infoA
0x00000000046f0600
&ReturnBuffer.infoB
0x00000000046f0608
&ReturnBuffer.number
0x00000000046f0610

in C, let say the function I am calling is taking parameter RETURNBUFFER *pReturnBuffer,

i get these address,

pReturnBuffer
0x00000000046F05F8 
&pReturnBuffer->records
0x00000000046F05F8 
&pReturnBuffer->infoA
0x00000000046F0600
&pReturnBuffer->infoB
0x00000000046F0610    **This address is off by 8**
&pReturnBuffer->number
0x00000000046F0620 **this is off by 16**

So as a result, when the code moves back to C# function,

I can construct the returnBuffer.records correctly, but weren't able to construct neither infoA nor infoB nor get the correct value for returnBuffer.number

not sure what I am missing out here.

===============================================

I edited my code with help of, Fun Mun Pieng

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct CRB
{
    [FieldOffset(0)]
    public IntPtr pcstat;//CSTAT
    [FieldOffset(8)]
    public IntPtr caProc;//NLCA
    [FieldOffset(24)] 
    public IntPtr caSent;//NLCA
    [FieldOffset(40)]
    public int cnlch;
}

now, the address matches up when it goes to C# -> C++ -> C# However I am still getting some garbage data back.

I did some investigation and here is the erratic behaviour I found.

in C# code i make the call like this

IntPtr text = Marshal.StringToCoTaskMemUni("I am here");

legacyFunction(text, ref returnBuffer)

in here, when I call the GetHashCode function, i get the following values

returnBuffer.records.GetHashCode()  473881344
returnBuffer.infoA.GetHashCode()  473898944
returnBuffer.infoB.GetHashCode()  473898784

text.GetHashCode() 468770816

upon returning from the function, these hash value changes,

returnBuffer.records.GetHashCode()  543431240
returnBuffer.infoA.GetHashCode()  473799988
returnBuffer.infoB.GetHashCode()  473799988

text.GetHashCode() 473799988

Now, I can actually do, Marshal.PtrToStringUni(checkReturnBuffer.infoA) and I get "I am here"

C# now thinks, both infoA and infoB are the same as text .

==================================================== 2nd edit

The c++ structure is in fact

typedef struct RETURNBUFFER
{
    RECORD *precord;
    INFO infoA;      
    INFO    infoB;       
    UINT    number;
 } CRB;

Thanks for the reply, this was indeed my problem.

I was somehow under the impression, for every struct/class/object in C++ I have to make an equivalent IntPtr in C#

One last question, while I am here so I dont have to re define all structs in a new question,

for the IntPtr in struct INFO. in C++, it is of type HANDLE

Am i correct here to define it as IntPtr? It is only a handle, but it is not a *handle thought, or should i just let it be a uint value?

Here's what I read from a msdn site "Remember, any API function that returns or accepts a handle is really working with an opaque pointer. Your code should marshal handles in Windows as System.IntPtr values"

If i defined it as IntPtr,

How should I alloc memory for it? Will the below be correct?

returnBuffer.infoA.doc = Marshal.AllocCoTaskMem(System.IntPtr.Size);

to unmarshal

Marshal.PtrToStructure(returnBuffer.infoA.doc, typeof(IntPtr));

is this the right approach?

Thank you so much

It might be because any of the following:

  1. your C++ is compiled to 16 bytes alignment
  2. your type for infoA is different between C++ and C#, or the size of the types are different

possible solutions include:

[FieldOffset(24)] public IntPtr infoB;

OR

comparing IntPtr.Size against sizeof(infoB) on C++


Edit : It seems infoA is 16 bytes, but your INFO declaration is not 16 bytes. It's very likely that your C# and C++ declarations are different. It would be good if you can include your C++ declarations in the question.

In the meantime, I can only guess the best match to be:

public struct RETURNBUFFER
{
    public RECORD records; //this is the struct RECORD
    public INFO infoA; // this is the struct INFO
    public INFO infoB; 
    public int number;
}

Your assumption that RETURNBUFFER contains structure pointers has to be wrong. That's the only way that infoA and infoB can take up 16 bytes. The INFO structure certainly is 16 bytes long, I can't see the type of infoB. Thus:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct RETURNBUFFER
    public IntPtr records;
    public INFO infoA;
    public DUNNO infoB; 
    public int number;
}

Update your question with the C declarations if you still have trouble. It should be easy to see from them.

Try to make sure the win32 struct and c# struct is bit(bit size) mapping. This can be achieved by using exact c# type for win32 type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM