1

The following is a bit of C++ that is verified working:

typedef struct
{
  PVOID  buffer;
  UINT32 length;
} DATA_BUFFER;

typedef struct 
{
  DATA_BUFFER TxBuf [1];
  DATA_BUFFER RxBuf [1];
} JVM_COMM_BUFFER;

UINT32 SendAndRecv(
  IN    JHI_HANDLE        handle,
  IN    CHAR*             AppId,
  INOUT JVM_COMM_BUFFER* pComm
);

The following is my attempt to port that to C#:

    [StructLayout(LayoutKind.Sequential)]
    public struct DATA_BUFFER
    {
        public byte[] buffer;
        public uint length;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct JVM_COMM_BUFFER
    {
        public DATA_BUFFER TxBuf;
        public DATA_BUFFER RxBuf;
    }

    [DllImport("jhi.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
    public static extern UInt32 SendAndRecv(IntPtr handle, string AppId, ref JVM_COMM_BUFFER pComm);

There is no exception thrown in the C# from marshalling, but the results are not the same for the C++ and C# versions. Any idea on what I'm missing?

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
Daniel
  • 73
  • 2
  • 10
  • What are the differences? What data do you put in and what does that get marshaled to? My first guess would be that you have to use `CharSet.Ansi`, since you have `CHAR` (and not `TCHAR` or `WCHAR`). – svick Oct 28 '11 at 00:38
  • I'm not sure the exact differences, all I know is that one returns success and the other returns Illegal Params, so something in the structs isn't getting communicated correctly. The CharSet shouldn't even matter in this instance since no chars are being used. – Daniel Oct 28 '11 at 00:46
  • 1
    So you're not sending any `AppId` in? – svick Oct 28 '11 at 00:50
  • Can't you just add a reference to your COM in visual studio? – nw. Oct 28 '11 at 00:57
  • @svick - Oh yeah, my bad, I did forget about that parameter, I was more focused on the buffers. – Daniel Oct 28 '11 at 16:39
  • @nw - no, I can't add a reference because it is an unmanaged dll – Daniel Oct 28 '11 at 16:40
  • @Daniel, you can add reference to unmanaged COM libraries. Have a look at the COM tab in the Add Reference dialog. – svick Oct 28 '11 at 16:43
  • @svick - It also wasn't signed with an assembly manifest, so it can't be added – Daniel Oct 28 '11 at 17:06
  • COMM != COM. The extra M makes it "communication". You don't pinvoke COM code. – Hans Passant Oct 29 '11 at 00:24

2 Answers2

0

The problem is probably in your first struct. The C++ definition includes a pointer to a data buffer, but the Marshaller can't directly convert this to a .NET byte[] (or, going the other direction it probably converts the byte[] to an invalid pointer, thus your Illegal Params error). Instead, you can do this manually in two steps:

[StructLayout(LayoutKind.Sequential)]
public struct DATA_BUFFER
{
    public IntPtr buffer;
    public uint length;
}

And then read the buffer manually using Marshal (this is just a quick sample from my memory of the Marshal API):

var txBufBytes = new byte[pComm.TxBuf.length];
Marshal.Copy(pComm.TxBuff.buffer, 0, pComm.TxBuf.length);

In addition to that, as @svick mentioned in the comments, you will probably need to set the CharSet to CharSet.Ansi if your native code is assuming non-unicode/wide characters.

Finally, the C++ definition of the second struct seems to define single-element arrays which might in turn actually be pointers, rather than having the structs in memory. If that is the case you will probably have to replace your definition to use IntPtrs for the interop and then use Marshal.PtrToStructure to get the actual structures.

You should at least compare that the struct sizes are the same by comparing the results of sizeof(...) in C++ with Marshal.SizeOf(...) in C# for the struct definitions.

Given what you've said in the comments, you should be able to use the DATA_BUFFER modification I described above, and the original struct definition you used for JVM_COMM_BUFFER

[StructLayout(LayoutKind.Sequential)]
struct JVM_COMM_BUFFER
{
    public DATA_BUFFER TxBuf;
    public DATA_BUFFER RxBuf;
}

Combine this with a slight modification to your DllImport

[DllImport("jhi.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern UInt32 SendAndRecv(IntPtr handle, string AppId, [In, Out] ref JVM_COMM_BUFFER pComm);

The CharSet.Ansi is important for ensuring your .NET string gets marshalled correctly to an ansi string (assuming your native C function doesn't expect a wchar_t string type). The [In, Out] attributes might not be required, but might hint to the marshaller how to control that parameter properly.

If the JVM_COMM_BUFFER is truly INOUT, and you're pre-populating it with data before calling the function, you may need to make sure the data is all valid. The function you're calling may have documentation on what values it expects its parameters to have. However, the definitions here should marshal correctly based on the C++ definitions you provided.

jeffora
  • 4,009
  • 2
  • 25
  • 38
  • I thought the same thing, but COM/Marshaling is confusing, so I thought I could get away with no pointers. I switched everything to pointers and started using Marshal.Copy to copy the buffer into a pointer, and Marshal.StructureToPtr to copy the structs, but it's still doing the same thing. Is it possible that the pComm parameter itself also needs to be an IntPtr, due to the fact that the C++ has an INOUT pointer reference? – Daniel Oct 28 '11 at 16:29
  • I compared the same struct in the 2 projects, and the size of the C++ buffer came out to 16, while the C# one was only 8. Does this have to do with the packing possibly? – Daniel Oct 28 '11 at 17:38
  • Firstly, I'm assuming this is running on x86 or a 4 byte pointer size. 16 bytes for the C++ struct would indicate the structs are contained within, as the first struct should be 8 bytes. You should still be able to use marshalling without having to revert to Marshal.Copy. I'll update the answer with some suggestions, but if you have access to the C++ COM code, try debugging the function to see what parameters the marshaller gives you from .NET – jeffora Oct 28 '11 at 23:27
  • Yes, it is running on x86. The C++ makes sense, I'm just not sure why the C# version is only 8 bytes instead of the 16 that it should be. Sadly I don't have access to the source of the COM library, that's why I'm kind of flying by the seat of my pants. It basically will only tell me that something is wrong, not what, or how to fix it. So what do you suggest instead of Marshal.Copy, especially since I have to translate buffers into IntPtrs? – Daniel Oct 28 '11 at 23:42
  • Edited the answer with some additional information. Also, I wasn't thinking straight re: Marshal.Copy, if your struct has an IntPtr to an array and the length of an array, Marshal.Copy is indeed the easiest way to get the array back into native code. Edited my original (stupid) for loop to indicate – jeffora Oct 29 '11 at 00:05
0

The issue is the difference between just allocating memory and allocating a pinned object. Once I switched it to a pinned object, then the signature without any IntPtrs except the buffer field worked just fine.

Daniel
  • 73
  • 2
  • 10