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();
}
Related
A C dll has:
struct CredPair{
char usr[127];
char pas[127];
};
struct CredData{
enum CredType {
PAIR,
KEY
} credType;
void* CredVal;
};
struct EndpointData {
enum EndpointType {
DIRECT
} endpointType;
void* endpointVal;
};
struct EndpointDirect {
char url[127];
};
I need to call a function in this dll from my C# code, which has the following signature:
__declspec(dllexport) MyErrCode CheckUser(const struct CredData* cred_data, const struct EndpointData* endpoint_data);
Here is what I have tried:
I first declared the corresponding types in C# :
public struct CredPair
{
public string usr;
public string pas;
}
public enum CredType
{
PAIR,
KEY
}
public struct EndpointDirect
{
public string url;
}
public enum EndpointType
{
DIRECT
}
public struct CredData
{
public CredType credType;
public IntPtr credVal;
}
public struct EndpointData {
public EndpointType endpointType;
public IntPtr endpointVal;
}
Later declared the function as:
[DllImport("mydll.dll")]
public static extern MyErrCode CheckUser(CredData cred_Data, EndpointData endpoint_data);
and then call the function as:
CredData objCredData = new CredData();
objCredData.credType = CredType.PAIR;
CredPair objPair = new CredPair();
objPair.usr = "abc#xyz.com";
objPair.pas = "admin#1234";
IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(objPair));
Marshal.StructureToPtr(objPair, pnt, false);
objCredData.credentialValue = pnt;
EndpointData objData = new EndpointData ();
objData.endpointType = EndpointType.DIRECT;
EndpointDirect epd = new EndpointDirect ();
epd.url = "example.com";
IntPtr urlptr = Marshal.AllocHGlobal(Marshal.SizeOf(epd));
Marshal.StructureToPtr(epd, urlptr, false);
objData.endpointValue = urlptr;
error_code = CheckUser(objCredData, objData);
But looks like parameters are not received correctly.
What parameters are wrong here? I believed void* in struct will become IntPtr in C#. The functions also need pointers to structure. Will that also have to be converted to IntPtr?
You have a number of issues with your PInvoke declarations
You need to make sure to free your HGlobal memory, otherwise it will leak.
The strings are declared as fixed size char arrays in C, so need to be CharSet.Ansi...
... and they need to be declared [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 127)] so they have fixed nested size.
The parameters to the function need to be ref, possibly with [In] attribute also.
You need CallingConvention = CallingConvention.Cdecl
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct CredPair
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 127)]
public string usr;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 127)]
public string pas;
}
public enum CredType
{
PAIR,
KEY
}
public enum EndpointType
{
DIRECT
}
public struct CredData
{
public CredType credType;
public IntPtr credVal;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct EndpointDirect
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 127)]
public string url;
}
public struct EndpointData
{
public EndpointType endpointType;
public IntPtr endpointVal;
}
[DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern MyErrCode CheckUser([In] ref CredData cred_Data, [In] ref EndpointData endpoint_data);
IntPtr pnt;
IntPtr urlptr;
try
{
CredPair objPair = new CredPair
{
usr = "abc#xyz.com"
pas = "admin#1234",
};
pnt = Marshal.AllocHGlobal(Marshal.SizeOf(objPair));
Marshal.StructureToPtr(objPair, pnt, false);
CredData objCredData = new CredData
{
credType = CredType.PAIR,
credVal = pnt,
};
endpointValue = new EndpointDirect
{
url = "example.com",
};
urlptr = Marshal.AllocHGlobal(Marshal.SizeOf(epd));
Marshal.StructureToPtr(epd, urlptr, false);
EndpointData objData = new EndpointData
{
endpointType = EndpointType.DIRECT,
endpointValue = urlptr,
};
error_code = CheckUser(ref objCredData, ref objData);
}
finally
{
Marshal.FreeHGlobal(pnt);
Marshal.FreeHGlobal(urlptr);
}
I am trying to marshal data from C# to C++ DLL. For which, I have following C++ code.
// C++ Function in DLL
int __declspec(dllexport) DataAnalyze(AllInfo *&all)
{
return 0;
}
// Structure
typedef struct
{
char model[100];
char log[_MAX_PATH];
char path[_MAX_PATH];
}AllInfo;
Here is my C# code.
public unsafe void Execute()
{
// Assigning values
public AllInfo ana_alls = new AllInfo();
AllInfo adt = new AllInfo(0);
adt.Eye_img_num = 5;
adt.Flow_log = #"D:\\HTML\\File.txt";
adt.Pano_path = #"D:\\HTML\\Img.bmp";
ana_alls = adt;
// Preparing data for marshalling
IntPtr intPtrsAll = new IntPtr();
intPtrsAll = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(AllInfo)));
Marshal.StructureToPtr<AllInfo>(ana_alls, intPtrsAll, false);
// DLL calling
int ret = 0 - DataAnalyze(ref intPtrsAll);
}
// Structure
[StructLayout(LayoutKind.Sequential)]
public struct AllInfo
{
public AllInfo(int n)
{
this.model = string.Empty;
this.path = string.Empty;
this.log = string.Empty;
}
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string model;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string log;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string path;
}
// DLL Calling
[DllImport(#"D:\\HTML\\Data\\DLL\\Dara_Dll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DataAnalyze(ref IntPtr ana_all);
In the C++ method, I am getting data in path of AllInfo *&all as follow:
path :{'\0','\0','\0','\0','\0','\0','\0','\0','D',':','\\','H','T','M','L','\\','I','m','g','.','b','m','p','\0','\0',........}
I am getting similar output for log also. I don't know why '\0' added from 0 to 7 index of each char[] data in C++ that is coming from C#. Where am I doing it wrong?
Using TwinCAT 3 ADS.Net for reading from PLC, I'm trying to read a struct containing array of structs, but the ReadAny command crashes with "Unable to marshal type" exception.
Reading directly an array of structs works fine though.
public object ReadAny(long indexGroup, long indexOffset, Type type, int[] args);
The header remark of the ReadAny method says:
“If the Type of the object to be read is an array type, the number of elements for each dimension has to be specified in the parameter args."
But what should args be for a struct containing array of structs?
(Without 'args' it fails too.)
I currently work with .NET 4.7, VS 2013.
Is there an option?
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public class WholeData
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public Station[] StationArray;
// Potentially more fields...
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public class Station
{
[MarshalAs(UnmanagedType.I1)]
public bool isPass;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
public string name;
// More fields...
}
// -- Main --
int[] args = { 5 };
// Works fine:
Station[] stationArray = (Station[])m_AdsClient.ReadAny(indexGroup, indexOffset, typeof(Station[]), args);
// Fail:
WholeData wholeData = (WholeData)m_AdsClient.ReadAny(indexGroup, indexOffset, typeof(WholeData), args);
// - OR -
WholeData wholeData = (WholeData)m_AdsClient.ReadAny(m_VarHandle, typeof(WholeData), args);
I tested successfully following code:
c# code:
class Program
{
public static TcAdsClient client;
static void Main(string[] args)
{
// Create the ADS Client
using (client = new TcAdsClient())
{
// Establish Connection
client.Connect(new AmsAddress("10.1.2.95.1.1", 851));
int handle = client.CreateVariableHandle("PRG_AIS.stAds");
AdsClass ads = (AdsClass)client.ReadAny(handle, typeof(AdsClass));
ads.boolArr[0] = 1;
client.WriteAny(handle, ads);
Console.ReadLine();
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
class AdsClass
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public byte[] boolArr = new byte[10];
}
ST code:
TYPE AdsStruct :
STRUCT
bTestArray : ARRAY[0..9] OF BOOL;
END_STRUCT
END_TYPE
AdsStruct is defined as stAds in PRG_AIS.
OR if you have an array of structs modify the code the following way:
c# code:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
class AdsClass
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public InnerStruct[] strArr = new InnerStruct[10];
}
struct InnerStruct
{
public byte bBoolTest;
public int nIntTest;
}
ST code:
TYPE AdsStruct :
STRUCT
stTestArray : ARRAY[0..9] OF InnerStruct;
END_STRUCT
END_TYPE
TYPE InnerStruct :
STRUCT
bBoolTest : BOOL;
nIntTest : DINT;
END_STRUCT
END_TYPE
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);
[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;
}