C# How to P/Invoke with struct array in struct? - c#

In C#, how to P/Invoke with struct array in struct?
C Lang defined struct is below...
struct 'OuterStruct'
int outerId
InnerStruct[10] innerStruct
struct 'InnerStruct'
int innerId
char[32] name
And C Lang defined Function is:
int ClangFunc(OuterStruct* arg)
'ClangFunc' is set values to 'arg'.
I call 'ClangFunc' from C#...
[DllImport("makefromclang.dll", EntryPoint="ClangFunc")]
public static extern int ClangFunc(IntPtr arg);
[StructLayout(LayoutKind.Sequential)]
public struct OuterStruct
{
public int outerId;
[MarshalAs(UnmanagementType.ByValArray, SizeConst=10)]
public InnerStuct[] innerStruct;
}
[StructLayout(LayoutKind.Sequential)]
public struct InnerStruct
{
public int innerId;
[MarshalAs(UnmanagementType.ByValTStr, SizeConst=32)]
public string name;
}
/* caller */
OuterStruct outerStruct = new OuterStruct();
IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(outerStruct));
Marshal.StructureToPtr(outerStruct, ptr, false);
int result = ClangFunc(ptr);
OuterStruct resultStruct = (OuterStruct)Marshal.PtrToStructure(ptr, typeof(OuterStruct));
Call ClangFunc is succeeded.
Results of OuterStruct.outerId and OuterStruct.innerStruct[0].innerId are set collect values.(in above resultStruct value)
But OuterStruct.innerStruct[0].name is null, why?.
I expected ""(empty string) or any Shift_JIS string. There's no way to set null value.

Thanks all.
The problem is that the value set in char[] is Shift_JIS charset.
When .NET converted char[] to string, it did not consider the character set.
As a result, the string value is corrupted, and the debugger seems to have a null string value.
To solve this problem, I modified the string mapped to the structure to byte[] and convert byte[] to string.
// [MarshalAs(UnmanagementType.ByValTStr, SizeConst=32)]
// public string name;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.ByValTStr, SizeConst=32)]
public byte[] name;
string str = System.Text.Encoding.GetEncoding("Shift_JIS").GetString(name);

Related

Marshal array of struct into Ptr

I am calling a C library from a C# code. The function I am calling take as parameter a struct containing arrays of struct :
struct Example1Struct
{
char* a;
uint16_t b;
AnotherStruct* c;
}
c here is an array of pointer to AnotherStruct.
the struct in my C# code look like this
public struct Example1Struct
{
public IntPtr StationName;//is char*
public UInt16 IdCode;
public IntPtr AnotherStruct; //array of struct AnotherStruct
}
public static IntPtr MarshalToPointer(object data)
{
Type valueType = data.GetType();
IntPtr buf = IntPtr.Zero;
if (valueType.IsArray)
{
if (data is char[])
{
var d = data as char[];
buf = Marshal.AllocHGlobal(Marshal.SizeOf(d.GetType().GetElementType()) * d.Length);
}
else if (data is char[,])
{
var d = data as char[,];
buf = Marshal.AllocHGlobal(Marshal.SizeOf(d.GetType().GetElementType()) * d.Length);
}
else
{
buf = Marshal.AllocHGlobal(Marshal.SizeOf(data.GetType().GetElementType()) * count);
long LongPtr = buf.ToInt64(); // Must work both on x86 and x64
for (int I = 0; I < data.Lenght; I++)
{
IntPtr RectPtr = new IntPtr(LongPtr);
Marshal.StructureToPtr(data[I], RectPtr, false); // You do not need to erase struct in this case
LongPtr += Marshal.SizeOf(typeof(Rect));
}
}
return buf;
}
else
buf = Marshal.AllocHGlobal(Marshal.SizeOf(data));
Marshal.StructureToPtr(data, buf, false);
return buf;
}
my problem here is that I cannot cast data (who is an array of AnotherStruct) to object[] , neither in IEnumerable. So I cannot access to data[I] and don't have data.Lenght
Any idea ?
Usually I'd recommend using the MarshalAs attribute rather than writing manual marshalling code. It looks like:
public struct Example1Struct
{
public IntPtr StationName;//is char*
public UInt16 IdCode;
public IntPtr AnotherStruct; //array of struct AnotherStruct
}
Could be:
public struct Example1Struct
{
[MarshalAs(UnmanagedType.LPStr)]
public string StationName;
public UInt16 IdCode;
[MarshalAs(UnmanagedType.LPArray)]
public AnotherStruct[] OtherStructs;
}
And the marshaller should do the right thing for you when you pass it to unmanaged code.
You can get the length of the array like this:
if (data is Array a)
Console.WriteLine(a.Length);
Arrays in c# always derive from Array, so you can cast it to that.
But if possible in your real code, I'd recommend Damien's answer

Marshal structure which contains strings - incorrect string received in unmanaged C function

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct sName_d
{
[MarshalAs(UnmanagedType.LPStr)]
public string szCountry;
[MarshalAs(UnmanagedType.LPStr)]
public string szCommonName;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct sCertData_d
{
public Int32 dwVersion;
public sName_d sIssuer;
public sName_d sSubject;
}
public void GenerateCert()
{
sCertData_d cert = new sCertData_d();
sName_d caIssuer = new sName_d();
caIssuer.szCountry = "US";
caIssuer.szCommonName = "John";
sName_d caSubject = new sName_d();
caSubject.szCountry = "UK";
caSubject.szCommonName = "Johann";
cert.sIssuer = caIssuer;
cert.sSubject= caSubject;
NativeMethods.GenerateCert(ref cert);
}
In the above code, NativeMethods.GenerateCert is an unmanaged function of C.
When Call reaches inside of this function, I am not getting the string values "John", "UK", "Johann" and "US".
[DllImport("AuthLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int GenerateCert(ref sCertData_d cert);
Unmanaged function prototype is like this -
typedef struct sName_d
{
char szCountry[0x35];
char szCommonName[0x35];
}sName_d;
typedef struct sCertData_d
{
int version;
sName_d sIssuer;
sName_d sSubject;
}sCertData_d;
int GenerateCert(const sCertData_d *psCert);
Your translation of sName_d is wrong. The unmanaged structure is:
typedef struct sName_d
{
char szCountry[0x35];
char szCommonName[0x35];
} sName_d;
These are inline character arrays. You marshaled these as UnmanagedType.LPStr. That's a pointer to null-terminated string. You need to use UnmanagedType.ByValTStr.
Used for in-line, fixed-length character arrays that appear within a structure. The character type used with ByValTStr is determined by the System.Runtime.InteropServices.CharSet argument of the System.Runtime.InteropServices.StructLayoutAttribute attribute applied to the containing structure. Always use the MarshalAsAttribute.SizeConst field to indicate the size of the array.
.NET Framework ByValTStr types behave like C-style, fixed-size strings inside a structure (for example, char s[5]).
Your translation should be:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct sName_d
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x35)]
public string szCountry;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x35)]
public string szCommonName;
}

Moving structure data in C#

Lets say I have the following structure in C
typedef struct
{
int field1;
char field2[16];
} MYSTRUCT;
Now I have a C routine that is called with a pointer to MYSTRUCT and I need to populate the structure, e.g.,
int MyCall(MYSTRUCT *ms)
{
char *hello = "hello world";
int hlen = strlen(hello);
ms->field1 = hlen;
strcpy_s(ms->field2,16,hello);
return(hlen);
}
How would I write MyCall in C#? I have tried this in Visual Studio 2010:
...
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
public struct MYSTRUCT
{
[FieldOffset(0)]
UInt32 field1;
[FieldOffset(4)]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
string field2;
}
public int MyProc(ref MYSTRUCT ms)
{
string hello = "hello world";
int hlen = hello.Length;
Marshal.Copy(hello, ms.field2, 0, hlen); // doesn't work
Array.Copy(hello, ms.field2, hlen); // doesn't work
// tried a number of other ways with no luck
// ms.field2 is not a resolved reference
return(hlen);
}
Thanks for any tips on the right way to do this.
Try changing the StructLayout.
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct MYSTRUCT
{
public UInt32 field1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
public string field2;
}
Since, you're passing as a reference, have you tried setting it as:
public int MyProc(ref MYSTRUCT ms)
{
string hello = "hello world";
ms.field2 = hello;
return hello.Length;
}
When using the ref keyword, you'll call MyProc like so:
static void Main(string[] args)
{
var s = new MYSTRUCT();
Console.WriteLine(MyProc(ref s)); // you must use "ref" when passing an argument
Console.WriteLine(s.field2);
Console.ReadKey();
}

Passing parameters from c# to c++

As the topic says, trying to pass a struct from c# environnement to c++.
c++ code that defines both the struct and the interface:
#pragma pack(push, 4)
struct CEA708CONFIG
{
BYTE b608Service;
BYTE bCompactStream;
BYTE pActiveServices[63];
LONG lActiveServiceCount; //
POINT ptAlignmentPosition;
};
#pragma pack(pop)
interface
__declspec(uuid("{some clsid}"))
ICEA708Decoder : IUnknown {
virtual HRESULT SetConfig(IN const CEA708CONFIG* pConfig) = 0;
virtual HRESULT GetConfig(OUT CEA708CONFIG* pConfig) = 0;
};
now to the c# code, i defined the same struct in c#
[StructLayout(LayoutKind.Sequential, Pack = 4), Serializable]
public struct CEA708CONFIG
{
public byte is608Service;
public byte isCompactStream;
//[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 63)]
public IntPtr activeServices;
public long activeServiceCount;
public Point alignmentPosition;
};
and the corresponding interface that accepts the config structure
[ComVisible(true), ComImport, Guid("same clsid as above"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICEA708Decoder
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int SetConfig([In, MarshalAs(UnmanagedType.Struct)] ref CEA708CONFIG config);
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int GetConfig([Out, MarshalAs(UnmanagedType.Struct)] out CEA708CONFIG config);
}
my problem occurs whenever i try to pass the structure, i can clearly see that while executing the c# code the entire struct is intialized with "reasonable" values, but once passed to the c++, i see that something has happened during the transaction.
the c# code that makes the magic happen:
CEA708CONFIG cc708Config;
ICEA708Decoder CC708DecoderConfig = CC708Filter as ICEA708Decoder;
if (CC708DecoderConfig == null)
{
throw new ApplicationException("Couldn't get ICEA708Decoder structure");
}
byte[] dataByte = new byte[63];
int size = Marshal.SizeOf(dataByte[0]) * dataByte.Length;
IntPtr pnt = Marshal.AllocHGlobal(size);
dataByte[0] = 1;
Marshal.Copy(dataByte, 0, pnt, dataByte.Length);
cc708Config.activeServices = pnt;
if (0 != (hr = CC708DecoderConfig.SetConfig(ref cc708Config)))
{
throw new ApplicationException("Couldn't SetConfig() because: " + DirectShowLib.DsError.GetErrorText(hr));
}
and the exception triggered by the SetConfig is:
{"Cannot marshal field 'activeServices' of type
'CCReIndexer.Graphs.CEA708CONFIG': Invalid managed/unmanaged type
combination (Int/UInt must be paired with SysInt or SysUInt).":""}
thanks for your help!!
Have you tried transfer array as array?
[StructLayout(LayoutKind.Sequential, Pack = 4), Serializable]
public struct CEA708CONFIG
{
public byte is608Service;
public byte isCompactStream;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 63)]
public byte[] activeServices;
public long activeServiceCount;
public Point alignmentPosition;
};
byte[] dataByte = new byte[63];
cc708Config.activeServices = dataByte;

Copying a string to a fixed length byte buffer in a structure

given this structure in c#:
[StructLayout(LayoutKind.Sequential)]
unsafe public struct AppVPEntry
{
public int Num;
public fixed byte CompName[256];
public int VPBeginAddress;
}
Whats the easiest way to copy a string ("c:\path\file.txt") to the fixed length buffer 'CompName'. This is in a structure thats being sent over to an archaic DLL that we've got no choice but to use. Ideally I'd love to use a .NET function but since it's fixed which implies 'unsafe' I know I'm limited here. A more generic function would help since we've got strings like this all over the DLL import space.
// C# to convert a string to a byte array.
public static byte[] StrToByteArray(string str)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
return encoding.GetBytes(str);
}
You probably want to check to see if the size of the string isn't longer than the size of the buffer.
Try this out. Use an IntPtr in your DllImport wherever you might pass a VPEntry. Pass the "unmanaged" field wherever you call your DLL method.
public sealed class AppVPEntry : IDisposable {
[StructLayout(LayoutKind.Sequential, Size = 264)]
internal struct _AppVPEntry {
[MarshalAs(UnmanagedType.I4)]
public Int32 Num;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public Byte[] CompName;
[MarshalAs(UnmanagedType.I4)]
public Int32 VPBeginAddress;
}
private readonly IntPtr unmanaged;
private readonly _AppVPEntry managed = new _AppVPEntry();
public AppVPEntry(Int32 num, String path, Int32 beginAddress) {
this.managed.Num = num;
this.managed.CompName = new byte[256];
Buffer.BlockCopy(Encoding.ASCII.GetBytes(path), 0, this.managed.CompName, 0, Math.Min(path.Length, 256));
this.managed.VPBeginAddress = beginAddress;
this.unmanaged = Marshal.AllocHGlobal(264);
Marshal.StructureToPtr(this.managed, this.unmanaged, false);
}
public void Dispose() {
Marshal.FreeHGlobal(this.unmanaged);
}
}

Categories