Calling C function with struct in C# - what am I doing wrong? - c#

I thought this one was fairly straight forward but still trying to understand all of this and having some issues.
I don't know much about the C function b/c i've been given limited information.
Here is the function call in C:
int GetCard(CardInfo card);
Here is the request structure:
typedef struct _tCardInfo
{
char CardNumber[80];
char isExist;
} TCardInfo, *pTCardInfo;
I want to pass the card number to see if it exists.
So in C# I did the following:
public struct CardInfo
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string cardNumber;
public byte isExist;
}
[DllImport("card.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public static extern int GetCardInfo(ref CardInfo cardInfo);
Then in the c# method:
CardInfo cardInfo = new CardInfo();
cardInfo.cardNumber = "1234567890";
int success = GetCardInfo (ref cardInfo);
The good thing about the DLL that I'm calling is it generates a log file.
When I execute the call, the log tells me that I'm hitting the DLL but it is not passing the card number which then sets a message saying the card number was not passed.
Any help is greatly appreciated.
Thanks!

The problem is that you're requesting TChar marshaling, but the DLL requires 8-byte characters. Change the C struct to wchar_t.
Also, use Visual Studio to set a breakpoint in your DLL, and actually inspect the data when it comes in! Visual Studio can debug across .NET/native boundaries, which is super cool!

Try to add attribute StructLayout for struct
[StructLayout(LayoutKind.Sequential)]
public struct CardInfo
{
...

Try to create the .Net struct like this:
[StructLayout(LayoutKind.Sequential)]
public struct CardInfo
{
[MarshalAs(UnmanagedType.AnsiBStr, SizeConst = 80)]
public string cardNumber;
[MarshalAs(UnmanagedType.I1)]
public sbyte isExist;
}
And for the function declaration: try not to use the CallingConvention and CharSet in the DLL import, and use the [In, Out] attributes before the parameter. Like this:
[DllImport("card.dll")]
public static extern int GetCardInfo([In, Out] CardInfo cardInfo);

Related

PInvoke calling of function with wchar16_t parameters

I have a .dll with C++ function which takes const wchar16_t* parameters.
I'm trying to import and use it in c# with string, char array, and char array with additional '\0' char but I got no result.
When I check it in original c++ program in debug mode it have additional '\0' char at the end. What exact type should I use?
P.s. I'm not 100% sure that problems arises because of these parameters.
I would very appreciate and give many points to rep if someone could kindly look into small projects I attach illustrating the problem. C++ program works fine (we getlogin response), but in c# project OnLoginResponseCallback is never fire.
What I do in C#
[DllImport("ActiveTickServerAPI.dll", CharSet = CharSet.Unicode, EntryPoint = "?ATCreateLoginRequest##YA_K_KPB_W1P6AX00PAU_ATLOGIN_RESPONSE###Z#Z", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
private static extern ulong ATCreateLoginRequest(ulong sessionId, string user, string pwd, ATLoginResponseCallback onLoginResponse);
public delegate void ATLoginResponseCallback(ulong hSession, ulong hRequest, ATLOGINRESPONSE response);
public delegate void ATRequestTimeoutCallback(ulong origRequest);
static void Main(string[] args)
{
lastRequest = ATCreateLoginRequest(hSession, userId, pw, OnLoginResponseCallback);
bool rc = ATSendRequest(hSession, lastRequest, 3000, OnRequestTimeoutCallback);
}
static void OnLoginResponseCallback(ulong hSession, ulong hRequest, ATLOGINRESPONSE response)
{
Console.WriteLine("!THIS SHOULD FIRE, but fire only timeout callback");
}
[StructLayout(LayoutKind.Sequential)]
public struct ATLOGINRESPONSE
{
public byte loginResponse;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
public byte[] permissions;
public ATTIME serverTime;
}
[StructLayout(LayoutKind.Sequential)]
public struct ATTIME
{
public ushort year;
public ushort month;
public ushort dayOfWeek;
public ushort day;
public ushort hour;
public ushort minute;
public ushort second;
public ushort milliseconds;
}
in c++ it's work (getting login response). Here is function description from api docs:
ACTIVETICKSERVERAPI_API uint64_t ATCreateLoginRequest ( uint64_t session,
const wchar16_t * userid,
const wchar16_t * password,
ATLoginResponseCallback pCallback
)
working struct from c++
typedef struct _ATLOGIN_RESPONSE
{
ATLoginResponseType loginResponse;
uint8_t permissions[255];
ATTIME serverTime;
} ATLOGIN_RESPONSE, *LPATLOGIN_RESPONSE;
typedef struct _ATTIME
{
uint16_t year;
uint16_t month;
uint16_t dayOfWeek;
uint16_t day;
uint16_t hour;
uint16_t minute;
uint16_t second;
uint16_t milliseconds;
} ATTIME, *LPATTIME;
I downloaded your test project and immediately fixed the delegate declarations because they were wrong:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATSessionStatusChangeCallback(ulong hSession, byte statusTyp);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATLoginResponseCallback(ulong hSession, ulong hRequest, ref ATLOGINRESPONSE response);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATRequestTimeoutCallback(ulong origRequest);
First time I ran the program I got:
SessionStatus - Connected
request timeout
!THIS SHOULD FIRE
SessionStatus - Connected
!THIS SHOULD FIRE
Second and subsequent times I got:
SessionStatus - Connected
!THIS SHOULD FIRE
No timeout. Why it behaved the way it did the first time is very hard to guess. You probably need to wait with the ATSendRequest() until you get confirmation of a proper login, something like that.
But clearly fixing the delegates took care of the problem, you are now getting the proper hSession value in the callback. It is not yet perfect, you need to ensure that they cannot be garbage collected. Store them in a static variable:
static ATSessionStatusChangeCallback statusCallback;
....
if (res == true) {
statusCallback = new ATSessionStatusChangeCallback(OnSessionStatusChangeCallback);
res = ATInitSession(sessionNumber, "activetick1.activetick.com",
"activetick2.activetick.com", 443, statusCallback, true);
}
Do the same thing on the 2 other ones.
The declaration of ATLOGINRESPONSE is almost certainly wrong.
public class ATLOGINRESPONSE
{
public byte loginResponse;
public byte[] permissions;
public ATTIME serverTime;
}
By declaring it as a class, you ensure that it will be marshalled by reference. But you preclude it ever being marshalled by value. Which might be fine. Anyway, I think I'd prefer to declare as a struct.
Further, the first two parameters look wrong to me. The first is probably an enum and should be declared as such. The second is a byte array but you need to specify how to marshal it. Like this:
[StructLayout(LayoutKind.Sequential)]
public struct ATLOGINRESPONSE
{
public ATLoginResponseType loginResponse; // you need to define the ATLoginResponseType enum
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
public byte[] permissions;
public ATTIME serverTime;
}
We don't know whether or not ATTIME is declared correctly.
I demangled your function name here: https://demangler.com/
The C++ function has this signature
unsigned __int64 __cdecl ATCreateLoginRequest(
unsigned __int64,
wchar_t const *,
wchar_t const *,
void (__cdecl*)(unsigned __int64,unsigned __int64,struct _ATLOGIN_RESPONSE *)
)
Your delegate is therefore declared with the wrong calling convention. It should be:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATLoginResponseCallback(
ulong hSession,
ulong hRequest,
ref ATLOGINRESPONSE response
);
Note that having changed ATLOGINRESPONSE to be a struct, we have to make the parameter a ref parameter.
The function ATLoginResponseCallback is declared correctly in your C# code.
Your other delegates will also need to be declared with [UnmanagedFunctionPointer(CallingConvention.Cdecl)].
Unless the callback is only called from ATCreateLoginRequest then the delegate that is passed is subject to being garbage collected. So if ATCreateLoginRequest takes a copy of that delegate, and calls later, then you will need to keep the delegate alive. Assign it to a variable of type ATLoginResponseCallback whose life extends beyond the final call to the callback.
It's entirely plausible that the problems lie elsewhere.

Using a C struct in C#

I am trying to port a windows-only USB driver to Linux by switching from hid.dll and other windows-only-stuff to the cross-platform hidapi.
hidapi.h contains the following definitions:
struct hid_device_info {
char *path;
unsigned short vendor_id;
unsigned short product_id;
wchar_t *serial_number;
unsigned short release_number;
wchar_t *manufacturer_string;
wchar_t *product_string;
unsigned short usage_page;
unsigned short usage;
int interface_number;
struct hid_device_info *next;
};
and
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
Please see the link above for the code with comments.
The driver is written in C#, so I would like to connect to the hidapi library and use these directly. I have managed to get access to hid_enumerate by doing this:
[DllImport ("/usr/local/lib/libhidapi-hidraw.so.0")]
internal static extern IntPtr hid_enumerate (ushort vendor_id, ushort product_id);
Calling hid_enumerate later then gives me a pointer. However, I am unsure how to proceed from here. I would like to turn this into a LinkedList of hid_device_info objects, or some other structure in C#.
I am quite new to C#, but have a lot of programming experience.
Could you please point me in the right direction?
Here's what I ended up doing. I made a struct in C# like this:
[StructLayout(LayoutKind.Sequential)]
private struct hid_device_info {
[MarshalAs(UnmanagedType.LPStr)]
public String path;
public ushort vendor_id;
public ushort product_id;
[MarshalAs(UnmanagedType.LPWStr)]
public String serial_number;
public ushort release_number;
[MarshalAs(UnmanagedType.LPWStr)]
public String manufacturer_string;
[MarshalAs(UnmanagedType.LPWStr)]
public String product_string;
public ushort usage_page;
public ushort usage;
public int interface_number;
public IntPtr next;
};
and when calling hid_enumerate, I did this to turn the pointer into an instance of the struct:
List<hid_device_info> list = new List<HidDeviceManager.hid_device_info> ();
IntPtr pDev = hid_enumerate (0, 0);
while (pDev != IntPtr.Zero) {
hid_device_info dev = new hid_device_info();
dev = (hid_device_info)Marshal.PtrToStructure (pDev, typeof(hid_device_info));
list.Add (dev);
pDev = dev.next;
}
It works well.

PInvoke does not change the object

I have the following PInvoke:(C to C#)
[DllImport("chess_api.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void InitBoard([MarshalAs(UnmanagedType.LPArray, SizeConst = 64)]sPiece[] board);
On C:
__declspec(dllexport) void InitBoard(sPiece board[8][8]);
In InitBoard function, the values ​​of the matrix changing, but after a call to PInvoke I do not see the change.
sPiece[] board = new sPiece[64];
InitBoard(board);
//Here the values ​​of the board is still initialized (as before the function call) at default values
I tried to change the variable to ref (although it already reference) but it stuck the program when the function was called, so I do not think it's the solution.
It took me a while to get here (I'm new to the subject) I'd love to help!
EDIT:
sPiece On C:
typedef struct Piece
{
ePieceType PieceType; //enum
ePlayer Player; //enum
int IsFirstMove;
} sPiece;
sPiece On C#:
[StructLayout(LayoutKind.Sequential)]
public struct sPiece
{
public ePieceType PieceType;
public ePlayer Player;
public int IsFirstMove;
}
Possibly you are failing to allocate memory before calling the function.
sPiece[] board = new sPiece[64];
InitBoard(board);
Declare the function like this:
[DllImport("chess_api.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void InitBoard([Out] sPiece[] board);
The default marshalling is [In]. Although since your struct is blittable, the array you pass is pinned and the call behaves as though it was [In,Out]. So I think you could omit [Out] if you wished, but it is clearer as written above.
You can add the UnmanagedType.LPArray option if you wish but it's not needed.

Accessing array in struct of C DLL from C#

I am trying to access and make changes to elements in a struct that are in a DLL. I have followed this example on how to use struct from a DLL: http://nawatt.com/index.php/corporate/blog/78-using-c-dlls-in-c
I have been successful in making changes to non-array variables, but whenever I try to make changes to an array I get a Runtime Error.
This is an example of my C DLL code:
//lib_qrs.dll
#ifdef __cplusplus
extern "C" {
#endif
typedef struct TEST_STRUCT
{
unsigned short check[5];
} test_struct;
__declspec(dllexport) void __stdcall test(test_struct *test, unsigned short val){
// This is an example of what DOES NOT WORK
test->check[0]=val+1;
test->check[1]=val+2;
test->check[2]=val+3;
test->check[3]=val+4;
test->check[4]=val+5;
}
#ifdef __cplusplus
}
#endif
This is an example of my C# code:
[StructLayout(LayoutKind.Sequential)]
public struct TEST_STRUCT
{
public UInt16[] check;
}
public class Program
{
[DllImport("lib_qrs.dll", EntryPoint="test", CallingConvention = CallingConvention.StdCall)]
public static extern void test(ref TEST_STRUCT test, int val);
public TEST_STRUCT testStruct = new TEST_STRUCT();
static void Main(string[] args)
{
testStruct.check=new UInt16[5];
// WHERE ERROR OCCURS
test(ref testStruct, 5);
}
}
The error that I get is:
*"An unhandled exception of type 'System.AccessViolationException' occurred in Test.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
I understand that I have to be very careful with memory allocation when I replicate my structure in C#, but I don't know what I am doing wrong or how I can fix this array issue.
Does anyone have an idea of how I could get around this?
The default marshaling for that array is unsigned short*, not unsigned short[]. You'll need to apply the [MarshalAs] attribute to tell the pinvoke marshaller about it. Fix:
[StructLayout(LayoutKind.Sequential)]
public struct TEST_STRUCT {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public UInt16[] check;
}
And since you are returning values in the array, you also have to tell the pinvoke marshaller that it needs to copy the array values back. That requires the [Out] attribute:
[DllImport("lib_qrs.dll")]
public static extern void test([Out] ref TEST_STRUCT test, int val);
Do note that neither is necessary at all if the you just declare the argument as ushort[], assuming that the structure doesn't contain any additional fields.

Struct from C++ to C#

I have a project which uses a library which was mostly build on and around C++.
The delivered DLL with this library, i have imported into my C# project.
After importing the following method in Unity:
[DllImport("pst")]
private static extern int pst_get_sensor(PSTSensor sensor);
I require this PSTSensor struct, so actually use the method.
In the C++ .h file, the struct is defines as :
struct PSTSensor
{
char name[80]; /**< Device name */
int id; /**< Device identifier (for other tracking interfaces */
float pose[16]; /**< Device pose estimate as row-major matrix */
double timestamp; /**< Time the data was recorded */
};
I have tried to replicate it in C#, and i ended up with this following:
struct PSTSensor{
PSTSensor(char[] name, int id, float[] pose, double timestamp){
this.name = name;
this.id = id;
this.pose = pose;
this.timestamp = timestamp;
}
public char[] name;
public int id;
public float[] pose;
public double timestamp;
}
In the example C++ code that came with this project was stated to call pst_get_sensor(&sensor) this '&' sign, i do not recognize? How would i call this method in C#, and make it work?
I think i ruined the struct, seeing how i never worked with them before. At least it doesn't throw errors on compilation anymore, but i think it is still wrong. Any ideas on that?
Many thanks in advance,
Smiley
I'm not sure if I'm answering your question entirely, but in c++ the & is used to pass an argument by reference which means that the argument you're passing in can be manipulated inside the function. To me it looks like the original function is used to fill out a sensor struct.
int C# you can pass by reference with the ref or out keyword:
private static extern int pst_get_sensor(PSTSensor ref sensor);
Why did you add a constructor in your C# implementation?
It seams that all you need to do is declare the parameter as ref
private static extern int pst_get_sensor(ref PSTSensor sensor);
If you want to have a look at an example, there is one here
You did not show the C++ declaration of your function, but give that you call it like this:
pst_get_sensor(&sensor);
it is presumably declared like so:
int pst_get_sensor(PSTSensor *sensor);
The function receives a pointer to the struct. That is clear since the call uses the & operator which takes the address of an object.
On the C# side you translate this parameter as a ref parameter. Like this:
[DllImport(...)]
static extern int pst_get_sensor(ref PSTSensor sensor);
Now, your other problem is that your struct is declared incorrectly. It contains inline arrays and you must communicate the lengths of those arrays to the marshaller. Like this:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
struct PSTSensor
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string name;
public int id;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public float[] pose;
public double timestamp;
}

Categories