I have the following C function:
int w_ei_connect_init(ei_cnode* ec, const char* this_node_name,
const char *cookie, short creation);
ei_cnode looks like this:
typedef struct ei_cnode_s {
char thishostname[EI_MAXHOSTNAMELEN+1];
char thisnodename[MAXNODELEN+1];
char thisalivename[EI_MAXALIVELEN+1];
char ei_connect_cookie[EI_MAX_COOKIE_SIZE+1];
short creation;
erlang_pid self;
} ei_cnode;
Which I have converted to C#:
[StructLayout(LayoutKind.Sequential)]
public struct cnode {
[MarshalAsAttribute(UnmanagedType.ByValTStr,
SizeConst = Ei.MAX_HOSTNAME_LEN + 1)]
public string thishostname;
[MarshalAsAttribute(UnmanagedType.ByValTStr,
SizeConst = Ei.MAX_NODE_LEN + 1)]
public string thisnodename;
[MarshalAsAttribute(UnmanagedType.ByValTStr,
SizeConst = Ei.MAX_ALIVE_LEN + 1)]
public string thisalivename;
[MarshalAsAttribute(UnmanagedType.ByValTStr,
SizeConst = Ei.MAX_COOKIE_SIZE + 1)]
public string ei_connect_cookie;
public short creation;
public erlang_pid self;
}
I'm not good with pointers or C in general, so I'm not sure how I'm supposed to supply a cnode to ei_connect_init.
What would the equivalent C# signature be for the C function above?
Whenever you want to pass a C# struct to a parameter value containing the equivalent native struct but with a pointer, the typical method is to label the parameter as "ref". This causes the PInvoke layer to essentially pass the address in.
[DllImport("somedll")]
public static extern w_ei_connect_init(
ref cnode v,
[In] string this_node_name,
[In] string cookie,
int16 creation);
Something like this should work:
int w_ei_connect_init(ref cnode ec,
[MarshalAs(UnmanagedType.LPStr)] string this_node_name,
[MarshalAs(UnmanagedType.LPStr)] string cookie, short creation);
You should also consider marking your struct with
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
attribute, so those TStr will be ansi-strings, not unicode.
Related
I'm trying to create a wrapper for a C dll to use it in C#. I don't have the source code of the dll.
I'm having problem using a method that gives me "System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt'" when executing. I tried to search online for the same problem but I didn't find what the problem with my code is.
The method that gives the problem is the following:
C:
L3B6_API L3B6_ERR_t STDCALL L3B6_ActivateBoot(int nodeid, unsigned cpunum, unsigned ms_timeout, const char * devicenames, A * dev_param);
the wrapper I created in C# is this:
[DllImport("L3B6.dll", EntryPoint = "L3B6_ActivateBoot")]
return: MarshalAs(UnmanagedType.I4)]
static extern L3B6ErrorCode ActivateBoot(
int nodeid,
uint cpunum,
uint msTimeout,
[MarshalAs(UnmanagedType.LPStr)]
string deviceNames,
ref A devParams
);
Where L3B6ErrorCode is an enum I created but it doesn't give any problem (I used it for other methods that work).
I think the problem is in the structure A. The original structure is like this:
struct A
{
unsigned char blv;
unsigned char cpunum;
unsigned char nodeid;
unsigned char hwverA;
unsigned char hwverB;
unsigned char hwverC;
unsigned char hwverD;
unsigned char cputype;
unsigned char hwcode;
B memini_devrec;
};
struct B
{
unsigned hwCode;
unsigned cpuCode;
unsigned dualCPU;
unsigned cpuNumber;
unsigned internalFlashStart;
unsigned internalFlashEnd;
unsigned externalFlashAccess;
unsigned externalFlashStart;
unsigned externalFlashEnd;
unsigned isResetVectorSpecified;
unsigned resetVectors;
char bootName[32];
unsigned isS19toPhyConversionAllowed;
unsigned noFlashPaging;
char deviceName[32];
char cpuName[32];
};
The corresponding structures I created are the following:
[StructLayout(LayoutKind.Sequential)]
public struct A
{
public byte bootloaderversion;
public byte cpuNum;
public byte nodeId;
public byte hwVerA;
public byte hwVerB;
public byte hwVerC;
public byte hwVerD;
public byte cpuType;
public byte hwCode;
public B meminiDeviceRecord;
};
[StructLayout(LayoutKind.Sequential)]
public struct B
{
public uint hwCode;
public uint cpuCode;
[MarshalAs(UnmanagedType.Bool)]
public bool dualCPU;
public uint cpuNumber;
public uint internalFlashStart;
public uint internalFlashEnd;
public uint externalFlashAccess;
public uint externalFlashStart;
public uint externalFlashEnd;
[MarshalAs(UnmanagedType.Bool)]
public bool isResetVectorSpecified;
public uint resetVectors;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string bootName;
[MarshalAs(UnmanagedType.Bool)]
public bool isS19toPhyConversionAllowed;
[MarshalAs(UnmanagedType.Bool)]
public bool noFlashPaging;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string deviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string cpuName;
};
I've used the structure B in another method that needed to fill it and didn't have any problem, so I suppose the problem is in the A structure, but I can't understand what I'm doing wrong. I think the problem is that structure because also another method that takes that structure as parameter is giving me the same exception. I checked the size of the structs in C and C# and it is the same.
I'm write a plugin with c# . the program with c# will use a dll which writes by c ,so I have to call c function in my c# program ,but unfortunately the c function's parameter is a struct and it is so complex that I never find any
help information about how to convert it to c# parameter.
the struct has an embedded function and void* parameter ,I didn't find anyway to convert them to c#.
the struct is mostly like this
struct first_struct{
char* parameter1;
int parameter2;
unsigned long parameter3;
unsigned short parameter4;
void* parameter5;
int (*parameter6)(int,void *);
second_struct parameter7;
};
struct second_struct{
char parameter8[64] ;
char parameter9[256];
};
I want change this c struct to c# struct but I have no idea how to do it
thanks #Biesi Grr and #Ian Abbott ‘s help it sames that I can change the c struct to c# like that.
public delegate int parameter6(int volcnt, System.IntPtr vod);
[StructLayout(LayoutKind.Sequential)]
public struct csharp_firstSturct
{
[MarshalAs(UnmanagedType.LPStr)]
public string parameter1;
public int parameter2;
public ulong parameter3;
public ushort parameter4;
public System.IntPtr parameter5;
public parameter6 m_parameter6;
}
but I still has no idea about how to convert parameter7 from C to C#,what should I do to convert parameter7 form c to c#.
Assuming your char arrays are null terminated strings you can do it the following way
public delegate int parameter6(int volcnt, System.IntPtr vod);
[StructLayout(LayoutKind.Sequential)]
public struct csharp_firstSturct
{
[MarshalAs(UnmanagedType.LPStr)]
public string parameter1;
public int parameter2;
public ulong parameter3;
public ushort parameter4;
public System.IntPtr parameter5;
public parameter6 m_parameter6;
[MarshalAs(UnmanagedType.Struct)]
public second_struct parameter7;
}
[StructLayout(LayoutKind.Sequential)]
public struct second_struct
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string parameter8;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string parameter9;
}
I'm trying to call a C DLL in my C# project using marshaling and have some functions working but I have trouble with others. Like the one below.
My first problem is getting the structs correct and next problem is to return PROFILE_INFO as an array with the list of program files or maybe it won't return a list and proNum is an index.
function in C
extern "C" __declspec(dllexport) int WINAPI GetProgramFileList (unsigned long proNum, PROFILE_INFO *proFile);
typedef struct{
PROINFO proInfo;
__int64 proSize;
PROGRAM_DATE createDate;
PROGRAM_DATE writeDate;
}PROFILE_INFO;
typedef struct{
char wno[33];
char dummy[7];
char comment[49];
char dummy2[7];
char type;
char dummy3[7];
}PROINFO;
typedef struct{
short year;
char month;
char day;
char hour;
char min;
char dummy[2];
}PROGRAM_DATE;
My function
[DllImport(#".\IFDLL.dll", EntryPoint = "GetProgramFileList", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern int GetProgramFileListTest(ulong proNum, ref PROFILE_INFO pro);
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROFILE_INFO
{
[MarshalAs(UnmanagedType.Struct)]
public PROINFO proInfo; // WNo/name/type
public long proSize; // Program size
[MarshalAs(UnmanagedType.Struct)]
public PROGRAM_DATE createDate; // Program creating date
[MarshalAs(UnmanagedType.Struct)]
public PROGRAM_DATE writeDate; // Program updating date
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROINFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string wno; // WNo.
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy; // dummy
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string comment; // program name
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy2; // dummy
public char type; // program type
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy3; // dummy
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROGRAM_DATE
{
public short year; // Date (Year) 4-digit
public char month; // Date (Month)
public char day; // Date (Day)
public char hour; // Date (Time)
public char min; // Date (Minutes)
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] private char dummy; // Dummy
}
The PROGRAM_DATE createDate in C# struct PROFILE_INFO will throw:
Cannot marshal field 'createDate' of type 'CClient.Models.PROFILE_INFO': The type definition of this field has layout information but has an invalid managed/unmanaged type combination or is unmarshalable.
Changing PROGRAM_DATE fields to string makes it accept it but the function returns an argument(-60) error instead. Though not sure if I'm closer to success.
Other attempts, including attempts to get PROFILE_INFO to return as an array (ref PROFILE_INFO[]), landed in:
A call to PInvoke function has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature.
I have got these descriptions following the C dll:
Explanation
Obtain the program list in the standard area
Argument
proNum [in]
Specify the number of the data to be obtained.
proFile [out]
Store the program information in the standard area, in PROFILE_INFO type.
Before executing this function, make sure to ensure the data area for the number of the data to be obtained.
Return value
If it succeeded, “0” is returned. If there is an error, a value other than “0” is returned.
Other functions I have working is GetProgramDirInfo, SendProgram, ReceiveProgram, SearchProgram, etc. but they don't return any arrays, so I assume marshaling arrays is my problem here. Also I'm trying to avoid using unsafe pointers and I'm unsure if I need to do the copying myself.
Any help is appreciated.
Few points when working with p/invoke:
don't add what's obvious for .NET (structs are structs, ...)
don't add pack if you're unsure, by default .NET should behave like C/C++ on that matter
don't add Ansi information if there's no string in the definition (only when there are strings or TStr, etc.). It doesn't cause problems but is useless
in general, don't add attributes if you don't know what they are used for
int and long in C/C++ are (in general) 32-bit. long is not 64-bit in C/C++
Here is a code that should be better:
[DllImport(#".\IFDLL.dll", EntryPoint = "GetProgramFileList")]
public static extern int GetProgramFileListTest(uint proNum, ref PROFILE_INFO pro);
[StructLayout(LayoutKind.Sequential)]
public struct PROFILE_INFO
{
public PROINFO proInfo; // WNo/name/type
public long proSize; // Program size
public PROGRAM_DATE createDate; // Program creating date
public PROGRAM_DATE writeDate; // Program updating date
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct PROINFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string wno; // WNo.
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy; // dummy
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string comment; // program name
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy2; // dummy
public char type; // program type
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy3; // dummy
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct PROGRAM_DATE
{
public short year; // Date (Year) 4-digit
public char month; // Date (Month)
public char day; // Date (Day)
public char hour; // Date (Time)
public char min; // Date (Minutes)
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] private string dummy; // Dummy
}
I have a C++ .dll (unable to make changes to the code) from which I am trying to call a function that takes a reference parameter of a Struct type (also defined in the .dll).
The function requires the Struct to have the 'paramName' and 'groupName' fields populated, and based on those fields, it will return the Struct with the remaining fields populated.
I am not getting any marshaling errors, however, the library call returns an error code and a debug log shows that it is receiving an empty string for the two string fields (see below for how fields are set). My assumption is that the size of this struct is not aligning between the managed and unmanaged representation, and thus the struct is not blittable.
Here is the C++ method signature:
int GetConfigs(int contextHandle, Configs* configs);
And the C++ Configs struct:
struct Configs {
int myInt;
float myFloat;
bool flag;
char name[64];
char group[64];
}
The C# function wrapper:
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int GetConfigs(int contextHandle, [MarshalAs(UnmanagedType.Struct), In, Out] ref Configs configs);
The C# struct definition:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Configs
{
public int myInt;
public float myFloat;
[MarshalAs(UnmanagedType.U1)]
public bool flag;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string group;
}
As per the Microsoft documentation I have declared the C++ char[]'s to be represented in C# by strings and marshaled as ByValTStr with a set size.
Also from Microsoft documentation:
bool is not blittable, so I mark it with an explicit MarshalAs.
float is also not blittable, but declaring these fields as a float or a double made no difference in the original issue.
Lastly, the C# calling code:
var configs = new Library.Configs
{
name = "testName",
group = "testGroup"
};
var returnCode = Library.GetConfigs(GetContextId(), ref configs);
The return code comes back as a failure code and the library debug output file shows that the struct argument had:
name = []
(name is clearly set to what I expect it to be at the time of the call when I debug through the C# code)
Instead of declaring the string fields as
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string name;
I tried:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public byte[] name;
but that also made no difference.
The float/double was what was throwing off the struct byte size- the library was expecting a 4 byte floating point number but the data marshaler by default keeps those as 8 bytes. Fixing that along with the boolean as mentioned in the comments above solved the problem:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Configs
{
public int myInt;
[MarshalAs(UnmanagedType.R4)]
public float myFloat;
[MarshalAs(UnmanagedType.U1)]
public bool flag;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string group;
}
In C++ header of a library there is the following code
#define STR_DATE 24+1
#define STR_SIZE 32+1
#define STR_SSIZE 64+1
#define STR_MSIZE 128+1
#define STR_LSIZE 1024+1
#define STR_IPSIZE 15+1
#define STR_MOD_SIZE 20+1
#define STR_AGESIZE 4+1
#define STR_GENDERSIZE 1+1
typedef struct ADO_PINFO{
char P_ID[STR_SSIZE];
char F_Name[STR_SSIZE];
char M_Name[STR_SSIZE];
char L_Name[STR_SSIZE];
char Reg_Num[STR_SSIZE];
UINT nGender;
UINT nAge;
COleDateTime BirthDay;
char csBirthDay[STR_SIZE];
COleDateTime V_Date;
char csV_Date[STR_SIZE];
char Address[_MAX_PATH];
char SubAddress[_MAX_PATH];
char Telephone[STR_SIZE];
char H_Phone[STR_SIZE];
char csMail[STR_SSIZE];
char csPicName[_MAX_PATH];
COleDateTime InDate;
char csInDate[STR_SIZE];
}*PADO_PINFO;
_ADODLL long ADO_AddPatientData(const ADO_PINFO &pPatientInfo);
I'm trying to import the dll to my C# application:
[StructLayout(LayoutKind.Sequential)]
public struct ADO_PINFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string P_ID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string F_Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string M_Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string L_Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string Reg_Num;
public uint nGender;
public uint nAge;
public DateTime BirthDay;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string csBirthDay;
public DateTime V_Date;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string csV_Date;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string Address;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string SubAddress;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string Telephone;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string H_Phone;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string csMail;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string csPicName;
public DateTime InDate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string csInDate;
}
public class VatechLibrary
{
[DllImport("AdodllE.dll")]
public static extern long ADO_AddPatientData(ref ADO_PINFO patientInfo);
}
But when I try co call it:
var pInfo = new ADO_PINFO();
pInfo.P_ID = "77";
pInfo.F_Name = "name";
var res = VatechLibrary.ADO_AddPatientData(ref pInfo);
I get AccessViolationException. What am I doing wrong?
The problems that I can see:
Your marshalling of the COleDateTime fields is wrong. That's because COleDateTime is a C++ class and they are simply not valid types for binary interop. And .net DateTime certainly does not match. That's surely the source of your access violation.
The function returns a C++ long which is 32 bits wide on Windows. So your C# function declaration is wrong because C# long is 64 bits wide. Change the return value in the C# to int.
Your C# calling convention is stdcall. What is the calling convention of the C++ function? That's presumably contained in _ADODLL. You'll need to check that it is stdcall. If the calling convention is not specified, it is cdecl.
The issue with COleDateTime is the big one here. The others are easily fixed. Not so for COleDateTime. You could change the C++ code to accept an interop friendly representation of the date. If you cannot change the C++ code to deal with the issue of item 1, then your solution will involve writing a mixed mode C++/CLI wrapper.
From the docs for 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]).
What is your charset? My guess is something that is unicode.
If I were to guess I would say you need to specify the character encoding as ANSI on the struct layout in the C# strcuture declaration. ByValTStr will carry the encoding of the containing struct and based on your C++ struct using char I think you need them enoded as Ansi.
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
From MSDN http://msdn.microsoft.com/en-us/library/s9ts558h(v=vs.110).aspx
UnmanagedType.ByValTStr
A fixed-length array of characters; the array's type is determined by
the character set of the containing structure.
You may also need to add Pack=1 to your StructLayout attribute.
Most of your strings/arrays are an odd number of bytes long and by default .NET will pad each of those out to an even byte boundary.
You should review the structure, the type COleDateTime is a class C++ and not have to create an attribute to interop directly. Review the items you need to access and redo the layout.