I have a DLL written in C++ (help file says so)
I have this .cs file that holds the function names and looks something like this:
*// -------------- Part of MXIO.cs File -----
//#define _NET_WINCE
//==========================================================================================
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace MOXA_CSharp_MXIO
{
class MXIO_CS
{
public const int SUPPORT_MAX_SLOT = 16;
public const int SUPPORT_MAX_CHANNEL = 64;
public const int SupportMaxChOfBit = SUPPORT_MAX_CHANNEL>>3;
//
#if _NET_WINCE
[StructLayout(LayoutKind.Explicit, Size = 4)]
#else
[StructLayout(LayoutKind.Explicit, Size = 4, Pack = 1)]
#endif
//V1.2 OPC Tag DATA Struct
#if _NET_WINCE
[StructLayout(LayoutKind.Sequential)]
#else
[StructLayout(LayoutKind.Sequential, Pack = 1)]
#endif
public struct _IOLOGIKSTRUCT
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] BytMagic;
public UInt16 wVersion; // struct define of version 1.0.0
public UInt16 wLength;
public UInt16 wHWID; // for user to know which API to Read/write
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] dwSrcIP;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] BytSrcMAC;
public byte BytMsgType; // for AP side to known what kind of Message return
public UInt16 wMsgSubType;
//------------------------
// tag timestamp
public UInt16 wYear;
public byte BytMonth;
public byte BytDay;
public byte BytHour;
public byte BytMin;
public byte BytSec;
public UInt16 wMSec;
//-------------------------
public byte BytLastSlot; //add to notice the last slot, Range 0-16, 0=>myself only
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SUPPORT_MAX_SLOT)]
public byte[] BytLastCh;
//-------------------------
// support up to 16 slots and 64 channels //size:5408 bytes
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SUPPORT_MAX_SLOT * SUPPORT_MAX_CHANNEL)]
public byte[] BytChType; // channel I/O type
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SUPPORT_MAX_SLOT)]
public UInt16[] wSlotID; // Slot Module ID
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SUPPORT_MAX_SLOT * SupportMaxChOfBit)]
public byte[] BytChNumber; // bitwised¡A1=Enable¡A0=Disable
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SUPPORT_MAX_SLOT * SUPPORT_MAX_CHANNEL)] //
public _ANALOG_VAL[] dwChValue;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SUPPORT_MAX_SLOT * SupportMaxChOfBit)]
public byte[] BytChLocked; // bitwised, 0=free, 1=locked
}
//
#if _NET_WINCE
[StructLayout(LayoutKind.Sequential)]
#else
[StructLayout(LayoutKind.Sequential, Pack = 1)]
#endif
public struct _ACTDEV_IO
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] BytSrcMAC;
public Int32 iHandle;
}
//
#if _NET_WINCE
[StructLayout(LayoutKind.Sequential)]
#else
[StructLayout(LayoutKind.Sequential, Pack = 1)]
#endif
public struct _MODULE_LIST
{
public UInt16 nModuleID; //Module ID of device
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] szModuleIP; //Save IP address of device
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] szMAC; //Save MAC address of device
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] szModuleIP1; //Save 2nd IP address of device
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] szMAC1; //Save 2nd MAC address of device
public byte bytLanUse; //0 -> Lan0, 1 -> Lan1
}
public delegate void pfnCALLBACK(IntPtr bytData, UInt16 wSize);
public delegate void pfnTagDataCALLBACK( _IOLOGIKSTRUCT[] ios, UInt16[] wMutex);
/**********************************************/
/* */
/* Ethernet Module Connect Command */
/* */
/**********************************************/
[DllImport("MXIO_NET.dll")]
public static extern int MXEIO_Init();
[DllImport("MXIO_NET.dll")]
public static extern void MXEIO_Exit();
[DllImport("MXIO_NET.dll")]
public static extern int MXEIO_Connect(byte[] szIP, UInt16 wPort, UInt32 dwTimeOut, Int32[] hConnection);
[DllImport("MXIO_NET.dll")]
public static extern int MXEIO_Disconnect( Int32 hConnection );
[DllImport("MXIO_NET.dll")]
public static extern int MXEIO_CheckConnection( Int32 hConnection, UInt32 dwTimeOut, byte[] bytStatus );
/***********************************************/
/* */
/* General Command */
/* */
/***********************************************/
[DllImport("MXIO_NET.dll")]
public static extern int MXIO_GetDllVersion();
[DllImport("MXIO_NET.dll")]
public static extern int MXIO_GetDllBuildDate();
/*************************************************/
/* */
/* Error Code */
/* */
/*************************************************/
public const int MXIO_OK = 0;
public const int ILLEGAL_FUNCTION = 1001;
public const int ILLEGAL_DATA_ADDRESS = 1002;
/*************************************************/
/* */
/* Data Format Setting */
/* */
/*************************************************/
/* Data length define */
public const int BIT_5 = 0x00;
public const int BIT_6 = 0x01;
public enum MXIO_ModuleDataIndex: int
{
//---------------------------------------------------------------------------
MX_ML_MODULE_LIST_SIZE = 47,
MX_ML_INDEX_WHWID = 0,
MX_ML_INDEX_SZIP0 = 2, //SIZE:16 (STRING)
MX_ML_INDEX_SZMAC0 = 18, //SIZE:6 (STRING)
MX_ML_INDEX_SZIP1 = 24, //SIZE:16 (STRING)
MX_ML_INDEX_SZMAC1 = 40, //SIZE:6 (STRING)
MX_ML_INDEX_BYTLANUSE = 46
};
//---------------------------------------------------------------------------
}
}*
I'm trying to access any of the functions inside the DLL. Here is the Delphi code:
type
TMyCall = function: integer; stdcall;
const
MYDLL = 'D:\DelphiProjects\DLL_Read\MXIO_NET.dll';
procedure TForm2.btnFncClick(Sender: TObject);
var
i,j,k: integer;
Handle: THandle;
mCall : TMyCall;
begin
// Load the library
Handle := LoadLibrary(MYDLL);
// If succesful ...
if Handle <> 0 then
begin
// Assign function from the DLL to the
// function variable mCall
#mCall := GetProcAddress(Handle, 'MXIO_GetDllVersion');
// If successful
if #mCall <> nil then
begin
Label3.Caption:= 'DLL version =' + IntToStr(mCall);
end
else
Label3.Caption:= 'Function Not found';
// Unload library
FreeLibrary(Handle);
end
else
Label3.Caption:= 'Version Handle = 0';
end;
Problem: GetProcAddress fails and returns nil.
I tried to cut the code into minimal and complete portion, hope I could do this time. Now I'm thinking to convert the .cs file (at least some part of it) into a Delphi unit and call the DLL functions from there. I wonder if anyone had experience on this? I'd appreciate any help
The question is somewhat confused. There is a huge amount of C# code that has no corresponding Delphi code. One wonders if the Delphi code really does exhibit the problem that you claim it does.
Anyway, on the assumption that the GetProcAddress call that you show does fail, then the question is easy enough to answer. GetProcAddress has two failure modes:
The module handle supplied is not valid.
The procedure name supplied is not valid.
We can rule out item 1 since the module handle was returned by a call to LoadLibrary. If LoadLibrary succeeds then we know that we have a valid module handle.
So, the only conclusion is that the procedure name supplied is not valid. Which means that the DLL that you have loaded does not export a function named MXIO_GetDllVersion. You can confirm this yourself by calling GetLastError immediately after GetProcAddress returns nil. When you do so you will get the value 127 which is ERROR_PROC_NOT_FOUND.
You can confirm this by inspecting the function names that the DLL exports. Use a tool such as Dependency Walker to do so.
One other minor comment on your Delphi code. You've used THandle for the module handle, but that is the wrong type. It won't affect behaviour but it is semantically wrong. Use HMODULE.
Related
Below is the C++ struct. Its implementation is done in a C++ DLL:
typedef char CusDate[sizeof("2023-02-02")];
typedef enum cycletype {
NOES=0,
LET=1,
WARN=2,
GRACE=3,
EXP=4,
} MYCYCLETYPE;
typedef struct Info {
MYCYCLETYPE midcycle;
int type;
int porotype;
long sways;
CusDate sedate;
long days;
CusDate elate;
long edays;
CusDate edate;
const char* feature;
const char* ver;
const char* hid;
}SINFO, *SINFO;
int Initialize(SINFO lp)
{
lp->mycycle = NOES;
......
.....
lp->hid = "XYZ";
}
Now, below is the C# code:
[DllImport(#"abc.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int Initialize(ref SINFO lp);
In C# main():
SINFO mydata;
Initialize(ref mydata);
Now, I am unable to access the struct data filled by C++ code in C#.
How to access the C++ struct data in C#?
typedef struct Info {
...
} SINFO, *SINFO;
The above declaration is not valid C/C++, as you can't have the same identifier (SINFO) refer to two different types (Info and Info*). Perhaps you meant something more like this instead:
typedef struct Info {
...
} SINFO, *PSINFO;
^
Or, the more C++-ish style (as the above style originates from C):
struct SINFO {
...
};
typedef SINFO* PSINFO;
// or: using PSINFO = SINFO*;
Either way, Initialize() can then take either a SINFO& reference or a PSINFO pointer as its parameter, either of which would be compatible with ref SINFO on the C# side:
int Initialize(SINFO& lp)
int Initialize(PSINFO lp)
Where the C# code would look something like this:
public enum MYCYCLETYPE {
NOES = 0,
LET = 1,
WARN = 2,
GRACE = 3,
EXP = 4,
};
[StructLayout(LayoutKind.Sequential)]
public struct SINFO {
public MYCYCLETYPE midcycle;
public int type;
public int porotype;
public long sways; // <-- or int, depending on the C++ compiler!
[MarshalAs(UnmanagedType.ByValArray, SizeConst=11)]
public byte[] sedate = new byte[11];
// alternatively:
// [MarshalAs(UnmanagedType.ByValArray, SizeConst=11, ArraySubType=UnmanagedType.LPStr)]
// public String sedate;
public long days; // <-- or int, depending on the C++ compiler!
[MarshalAs(UnmanagedType.ByValArray, SizeConst=11)]
public byte[] elate = new byte[11];
// alternatively:
// [MarshalAs(UnmanagedType.ByValArray, SizeConst=11, ArraySubType=UnmanagedType.LPStr)]
// public String elate;
public long edays; // <-- or int, depending on the C++ compiler!
[MarshalAs(UnmanagedType.ByValArray, SizeConst=11)]
public byte[] edate = new byte[11];
// alternatively:
// [MarshalAs(UnmanagedType.ByValArray, SizeConst=11, ArraySubType=UnmanagedType.LPStr)]
// public String edate;
public IntPtr feature; // <-- use Marshal.PtrToStringAnsi() to read this
// alternatively:
// [MarshalAs(UnmanagedType.LPStr)]
// public String feature;
public IntPtr ver; // <-- use Marshal.PtrToStringAnsi() to read this
// alternatively:
// [MarshalAs(UnmanagedType.LPStr)]
// public String ver;
public IntPtr hid; // <-- use Marshal.PtrToStringAnsi() to read this
// alternatively:
// [MarshalAs(UnmanagedType.LPStr)]
// public String hid;
};
[DllImport("abc.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int Initialize(ref SINFO lp);
Also, on a side note: you have Initialize() declared as returning an int, but the C++ code you have shown is not actually return'ing a value, which is undefined behavior.
I am trying to convert c++ api example to c#. but I can not find the way of struct pointer in struct by the way struct has string types. Here is c++ structs and functions
struct AuthParam { char server_ip[32]; char username[50]; char password[50]; };
struct CameraInfo { int index; char devicename[100]; char smallrtsp[1000]; char bigrtsp[1000]; };
struct SingleDevice { char deviceid[50]; char devicename[100]; int flag_onuse; int cameralist_size; CameraInfo* cameralist; };
struct DeviceList { int listsize; SingleDevice* singledevicelist; };
typedef int (WINAPI capi_init)(void);
typedef int (WINAPI capi_disabled)(void);
typedef int (WINAPI capi_GetServerTimeCode)(char* server_ip, unsigned int* timecode);
typedef int (WINAPI GetDevicelist)(AuthParam auth_para, DeviceList* devicelist);
and this is my c# code but I can not find the way of defining struct pointer in struct I got "
Error CS0208 Cannot take the address of, get the size of, or declare a pointer to a managed type ('Form1.CameraInfo')" error.
public struct AuthParam
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string server_ip;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string username;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string password;
};
[StructLayout(LayoutKind.Sequential)]
public struct CameraInfo
{
public int index;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string devicename;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1000)]
public string smallrtsp;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1000)]
public string bigrtsp;
};
[StructLayout(LayoutKind.Sequential)]
public struct SingleDevice
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string deviceid;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string devicename;
public int flag_onuse;
public int cameralist_size;
public CameraInfo *cameralist;
};
[StructLayout(LayoutKind.Sequential)]
public unsafe struct DeviceList
{
public int listsize;
public SingleDevice *singledevicelist;
};
[DllImport("c:\\lib\\api_client.dll")] public static extern int capi_init();
[DllImport("c:\\lib\\api_client.dll")] public static extern int capi_disabled();
[DllImport("c:\\lib\\api_client.dll")] public static extern int capi_GetServerTimeCode(string ip, ref uint timecode);
[DllImport("c:\\lib\\api_client.dll")] public static extern int GetDevicelist (AuthParam auth_para,ref DeviceList devicelist);
is there any way to achieve this convertion?
i think this error because of the different nature of c++ (un-managed code) and c# (managed code), so maybe using System.IntPtr as a pointer to your camera struct.
please consider this question here, and you can find about P/Invoke Interop Assistant which is an open source tool to convert your code from the un-managed code to a managed C# code. with a small blog article about C++/C# interoperability
I'm trying to get the output of uname -r in C# in .NET Core 2.2 running on Ubuntu 18.04.
I'm writing this with performance in mind, so have been trying to use a P/Invoke to achieve it.
The uname(2) docs indicate I need to pass a struct in with the relevant sized fields. After playing with a lot of variations, I came up with:
[StructLayout(LayoutKind.Sequential)]
unsafe internal struct Utsname
{
public fixed byte sysname[65];
public fixed byte nodename[65];
public fixed byte release[65];
public fixed byte version[65];
public fixed byte machine[65];
}
public static class Main
{
[DllImport("libc.so.6", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void Main(string[] args)
{
byte[] bs = new byte[65];
unsafe
{
var buf = new utsname();
uname(ref buf);
Marshal.Copy((IntPtr)buf.release, bs, 0, 65);
}
Console.WriteLine(Encoding.UTF8.GetString(bs));
}
}
This seems to work, but moving it into a wrapper function like:
public static class Main
{
...
public static string GetUnameRelease()
{
var bs = new List<byte>();
unsafe
{
var buf = new utsname();
uname(ref buf);
int i = 0;
byte* p = buf.release;
while (i < 65 && *p != 0)
{
bs.Add(*p);
p++;
i++;
}
}
return Encoding.UTF8.GetString(bs.ToArray());
}
public static void Main(string[] args)
{
Console.WriteLine(GetUnameRelease());
}
}
Seems to cause it to fail. I'm just not sure what I'm doing wrong. It fails silently, presumably due to a segfault, although I'm not sure where/how to get a trace of that.
Other struct marshalling methods I've tried
I also tried a few other ways to get the struct back.
The simplest seemed to be the string fields with fixed-length values (but I assume this fails because the caller needs to allocate mutable fields for the callee to set):
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string sysname;
...
}
Or a simple byte array:
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 65)]
public byte[] sysname;
...
}
In this case, I assume the problem is something to do with the In/Out calling convention when passing a managed array into the call.
I tried using out instead of ref to simplify the P/Invoke as well, but I get the impression uname() expects the caller to allocate the memory before the call.
I also tried using the [In] and [Out] attributes, but not sure what the defaults are or how using them would change things.
Writing an external C library to wrap the call
I also wrote a small C library to wrap the call to make the calling convention easier to handle:
#include <string.h>
#include <stdlib.h>
#include <sys/utsname.h>
char *get_uname_release()
{
struct utsname buf;
uname(&buf);
size_t len = strlen(buf.release);
char *release = malloc(len * sizeof(char));
strcpy(release, buf.release);
return release;
}
I compiled this with gcc -shared -o libget_uname.so -fPIC get_uname.c and put it next to the main managed DLL.
Calling this was much easier, with just:
public static class Main
{
...
[DllImport("libget_uname.so", EntryPoint = "uname_get_release", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern string GetUnameRelease();
}
This seemed to work every time I used it.
But I'm averse to including a native library in code, if it might be possible to just P/Invoke directly instead.
Using a Process call instead
The other obvious simple choice would just be to call the uname coreutil as a subprocess:
public static class Main
{
...
public static string GetUnameRelease()
{
var unameProc = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "uname",
Arguments = "-r",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
unameProc.Start();
unameProc.WaitForExit();
return unameProc.StandardOutput.ReadToEnd();
}
}
But I was hoping to avoid the overhead of a subprocess... Perhaps it's not so bad on Linux and just worth doing?
But I've spent a while looking into the PInvoke now, so I would like to know if it's possible.
Questions
So my questions are:
What's the best (fastest reliable) way to get the release field from uname from C#?
How would I P/Invoke the uname() syscall in libc reliably to get the utsname struct back?
Version that doesn't require unsafe code nor Mono:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace UnameTest
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct Utsname
{
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] sysname;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] nodename;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] release;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] version;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] machine;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 65)]
public byte[] domainname;
}
public static class Uts
{
[DllImport("libc", EntryPoint = "uname", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void PrintUtsname()
{
Utsname buf = new Utsname();
uname(ref buf);
Console.WriteLine($"Utsname:");
Console.WriteLine($"---------------------------------");
Console.WriteLine($"sysname: {GetString(buf.sysname)}");
Console.WriteLine($"nodename: {GetString(buf.nodename)}");
Console.WriteLine($"release: {GetString(buf.release)}");
Console.WriteLine($"version: {GetString(buf.version)}");
Console.WriteLine($"machine: {GetString(buf.machine)}");
Console.WriteLine($"domainname: {GetString(buf.domainname)}");
}
private static string GetString(in byte[] data)
{
var pos = Array.IndexOf<byte>(data, 0);
return Encoding.ASCII.GetString(data, 0, (pos < 0) ? data.Length : pos);
}
}
}
The reason it is not working when you move the code to a function is that your structure does not include the domainname member, so when you call uname it is clobbering memory beyond the memory you allocated for your structure.
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
unsafe internal struct Utsname
{
public fixed byte sysname[65];
public fixed byte nodename[65];
public fixed byte release[65];
public fixed byte version[65];
public fixed byte machine[65];
public fixed byte domainname[65];
}
public static class Program
{
[DllImport("libc.so.6", CallingConvention = CallingConvention.Cdecl)]
internal static extern int uname(ref Utsname buf);
public static void Main(string[] args)
{
Console.WriteLine(GetUnameRelease());
}
static unsafe string GetUnameRelease()
{
Utsname buf;
uname(ref buf);
return Marshal.PtrToStringAnsi((IntPtr)buf.release);
}
}
Here's a version that doesn't require unsafe code:
public class Utsname
{
public string SysName; // char[65]
public string NodeName; // char[65]
public string Release; // char[65]
public string Version; // char[65]
public string Machine; // char[65]
public string DomainName; // char[65]
public void Print()
{
System.Console.Write("SysName:\t");
System.Console.WriteLine(this.SysName);
System.Console.Write("NodeName:\t");
System.Console.WriteLine(this.NodeName);
System.Console.Write("Release:\t");
System.Console.WriteLine(this.Release);
System.Console.Write("Version:\t");
System.Console.WriteLine(this.Version);
System.Console.Write("Machine:\t");
System.Console.WriteLine(this.Machine);
System.Console.Write("DomainName:\t");
System.Console.WriteLine(this.DomainName);
Mono.Unix.Native.Utsname buf;
Mono.Unix.Native.Syscall.uname(out buf);
System.Console.WriteLine(buf.sysname);
System.Console.WriteLine(buf.nodename);
System.Console.WriteLine(buf.release);
System.Console.WriteLine(buf.version);
System.Console.WriteLine(buf.machine);
System.Console.WriteLine(buf.domainname);
}
}
[System.Runtime.InteropServices.DllImport("libc", EntryPoint = "uname", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
private static extern int uname_syscall(System.IntPtr buf);
// https://github.com/jpobst/Pinta/blob/master/Pinta.Core/Managers/SystemManager.cs
private static Utsname Uname()
{
Utsname uts = null;
System.IntPtr buf = System.IntPtr.Zero;
buf = System.Runtime.InteropServices.Marshal.AllocHGlobal(8192);
// This is a hacktastic way of getting sysname from uname ()
if (uname_syscall(buf) == 0)
{
uts = new Utsname();
uts.SysName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(buf);
long bufVal = buf.ToInt64();
uts.NodeName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 1 * 65));
uts.Release = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 2 * 65));
uts.Version = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 3 * 65));
uts.Machine = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 4 * 65));
uts.DomainName = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new System.IntPtr(bufVal + 5 * 65));
if (buf != System.IntPtr.Zero)
System.Runtime.InteropServices.Marshal.FreeHGlobal(buf);
} // End if (uname_syscall(buf) == 0)
return uts;
} // End Function Uname
I am trying to call a C function from C# code:
typedef char IPTriggerNameType[256];
typedef unsigned long COMM_HANDLE;
typedef BYTE GUID_TYPE[16];
typedef long LONGINT;
typedef struct {
GUID_TYPE Guid;
IPTriggerNameType Name;
}IPCAM_GENERIC_EVENT_ID;
typedef struct {
IPCAM_GENERIC_EVENT_ID EventId;
LONGINT RelatedTriggerId;
LONGINT ObsoleteEvent;
}IPCAM_GENERIC_EVENT_INFO;
typedef struct
{
LONGINT NumOfEvents;
IPCAM_GENERIC_EVENT_INFO *GenericEventsList;
}VID_CHANNEL_GENERIC_EVENTS_STRUCT;
int __stdcall GetGenericEvents(
/* Inputs: */
COMM_HANDLE Handle,
LONGINT MaxNumOfChannelsInTable,
LONGINT MaxNumOfEventsPerChannel,
/* Outputs: */
LONGINT *NumOfChannels,
VID_CHANNEL_GENERIC_EVENTS_STRUCT *ChannelsEventsTable);
and the C# equivalent is as follows:
[StructLayout(LayoutKind.Sequential)]
public struct IPCAM_GENERIC_EVENT_ID
{
[MarshalAs(UnmanagedType.LPArray, SizeConst = 16)]
public byte[] Guid;
[MarshalAs(UnmanagedType.LPArray, SizeConst = 256)]
public char[] Name;
};
[StructLayout(LayoutKind.Sequential)]
public struct IPCAM_GENERIC_EVENT_INFO
{
public IPCAM_GENERIC_EVENT_ID EventId;
public int RelatedTriggerId;
public int ObsoleteEvent;
}
[StructLayout(LayoutKind.Sequential)]
public struct VID_CHANNEL_GENERIC_EVENTS_STRUCT
{
public int NumOfEvents;
[MarshalAs(UnmanagedType.LPArray, SizeConst = 100)]
public IPCAM_GENERIC_EVENT_INFO[] GenericEventsList;
}
[DllImport(dllName)]
public static extern int GetGenericEvents(
/* Inputs: */
int Handle,
int MaxNumOfChannelsInTable,
int MaxNumOfEventsPerChannel,
/* Outputs: */
out int NumOfChannels,
out VID_CHANNEL_GENERIC_EVENTS_STRUCT[] ChannelsEventsTable);
int numOfChannels = 16, actualNumOfChannels = 0;
int maxNumOfEvents = 100;
VID_CHANNEL_GENERIC_EVENTS_STRUCT[] genericEventsList = new VID_CHANNEL_GENERIC_EVENTS_STRUCT[numOfChannels];
for (int i = 0; i < numOfChannels; i++)
{
genericEventsList[i].GenericEventsList = new IPCAM_GENERIC_EVENT_INFO[maxNumOfEvents];
}
GetGenericEvents(conn, numOfChannels, maxNumOfEvents, out actualNumOfChannels, out genericEventsList);
when calling the C# method I get exception which crashes the application:
Managed Debugging Assistant 'FatalExecutionEngineError' has detected a
problem in 'My.exe'.
Additional information: The runtime has encountered a fatal error. The
address of the error was at 0x72a6cf41, on thread 0x5a98. The error
code is 0xc0000005. This error may be a bug in the CLR or in the
unsafe or non-verifiable portions of user code. Common sources of this
bug include user marshaling errors for COM-interop or PInvoke, which
may corrupt the stack.
What am I doing wrong ?
The field:
VID_CHANNEL_GENERIC_EVENTS_STRUCT.GenericEventsList
is a pointer, declared as:
IPCAM_GENERIC_EVENT_INFO *GenericEventsList
But you are declaring it as an array, included in the struct:
[MarshalAs(UnmanagedType.LPArray, SizeConst = 100)]
public IPCAM_GENERIC_EVENT_INFO[] GenericEventsList;
You may try to use an IntPtr and Marshal.AllocHGlobal. Check this out: How use pinvoke for C struct array pointer to C#
I've struggled with at p/invoke problem for some time now. Keep getting "Attempted to read or write protected memory." all the time, and I suspect my marshaling is a bit off.
Here is the c signature:
long ft_show( /* show file-attributes */
const struct ft_admission *admis, /* I: transfer admission */
const struct ft_shwpar *par, /* IO: parameter-list */
struct ft_fileinfo *info, /* O: requested information */
struct ft_err *errorinfo, /* O: error information */
void *options /* I: options */
);
and here are the related structures (in c)
struct ft_admission
{
char *remsys;
char *remadmis;
char *remaccount;
char *rempasswd;
};
struct ft_shwpar
{
int shwparvers;
char *fn;
char *mgmtpasswd;
char *fud;
int fudlen;
};
struct ft_fileinfo
{
int ftshowivers;
char fn[INFO_FN_LEN];
enum ft_ftype filetype;
enum ft_charset charset;
enum ft_rform recordform;
long recsize;
enum ft_available availability;
int access;
char account[ACC_LEN];
long size;
long maxsize;
char legalqual[LQ_LEN];
char cre_user[USER_LEN];
long cre_date;
char mod_user[USER_LEN];
long mod_date;
char rea_user[USER_LEN];
long rea_date;
char atm_user[USER_LEN];
long atm_date;
long long fsize;
long long fmaxsize;
};
struct ft_err
{
long main;
long detail;
long additional;
};
struct ft_options
{
int ftoptsvers;
int ftapivers;
};
I've tried to create c# structs and calls like this:
[DllImport("ftapi.dll", EntryPoint = "ft_showdir")]
private static extern long ft_showdir(ft_admission admis, ref ft_shwpar par, ft_fileinfo[] buf, int bufsize, ref ft_err errorinfo, ft_options options);
public struct ft_admission
{
public String remsys;
public String remadmis;
public String remaccount;
public String rempasswd;
};
public struct ft_shwpar
{
public int shwparvers;
public String fn;
public String mgmtpasswd;
public IntPtr fud;
public int fudlen;
};
[StructLayout(LayoutKind.Sequential, Size = 1464, CharSet = CharSet.Ansi), Serializable]
public struct ft_fileinfo
{
public int ftshowivers;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
public String fn;
public ft_ftype filetype;
public ft_charset charset;
public ft_rform recordform;
public long recsize;
public ft_available availability;
public int access;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public String account;
public long size;
public long maxsize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
public String legalqual;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
public String cre_user;
public long cre_date;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
public String mod_user;
public long mod_date;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
public String rea_user;
public long rea_date;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
public String atm_user;
public long atm_date;
public long fsize;
public long fmaxsize;
};
public struct ft_err
{
public long main;
public long detail;
public long additional;
}
public struct ft_options
{
public int ftoptsvers;
public int ftapivers;
};
I've already used the ft_err and ft_options structs successfully in another call, so I think those are marshaled correctly.
I think the problem is the ft_fileinfo structure. I want to use StringBuilder in there, but there is a bug in the framework preventing correct marshaling of StringBuilder inside structs. I've tried the workarounds described here: http://support.microsoft.com/kb/327109
but no success.
Here's the code snippet provided in the documentation of the api I try to access (some code has been removed as it's only used for printing info to the screen):
/* sample3.c File management requests */
/*************************************************************/
static const char Sccsid[] = "#(#)sample3.c 2.33 2009/03/25";
/*************************************************************
Program call: sample3 <directory>
*************************************************************/
/* Include Files */
/*************************************************************/
#include <stdio.h> /* printf() */
#include <stdlib.h> /* exit() */
#include <string.h> /* strcat(), strcpy() */
#include <ftapi.h>
/*************************************************************/
/* Local Constants and Macros */
/*************************************************************/
#define BUFSIZE 200 /* number of ft_fileinfo structures */
/* in output buffer */
/*************************************************************/
/* Local Functions Declarations */
/*************************************************************/
static void error_print(struct ft_err *, char *);
static void printinfo(struct ft_fileinfo *info);
int main (int argc, char *argv[])
{
int i; /* Some local counter */
int count; /* Number of files */
char remotesys[201]; /* Address of remote systems */
char remoteadm[68]; /* Transfer admission */
char fud[STAT_FUD_LEN]; /* Further details */
struct ft_err errorinfo; /* Error information */
struct ft_admission admis; /* Admission parameters */
struct ft_shwpar par; /* Show directory parameters */
struct ft_options opt; /* Options */
struct ft_fileinfo buf[BUFSIZE]; /* Output buffer */
/* Check program arguments */
if (argc != 2)
{
printf("Call: %s <directory>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Initialize ft_transpar structure */
memset(&par, 0, sizeof(par));
/* Set version of the parameter list */
par.shwparvers = FT_SPARV2;
/* Set the name of the remote directory */
par.fn = argv[1];
/* Set buffer and buffer length for the further details */
par.fud = fud;
par.fudlen = sizeof(fud);
/* Set version of the ft_fileinfo structure in the */
/* output buffer. The version in the first structure */
/* is valid for all structtures in the output buffer. */
buf[0].ftshowivers = FT_SHOWIV2;
/* Get the name/address of the remote system and the */
/* transfer admission */
printf("Enter name of remote system: ");
scanf("%s", remotesys);
printf("Input transfer admission to remote system: ");
scanf("%s", remoteadm);
admis.remsys = remotesys;
admis.remadmis = remoteadm;
admis.remaccount = NULL;
admis.rempasswd = NULL;
/* Prepare the options structure */
opt.ftoptsvers = FT_OPTSV1;
opt.ftapivers = FT_APIV2;
/* Read the contents of the remote directory */
count = ft_showdir(&admis, &par, buf, BUFSIZE, &errorinfo,
&opt);
if (count == -1)
{
/* Error */
error_print(&errorinfo, par.fud);
}
/* Display result of request */
printf("There are %d entries in remote directory %s\n",
count, argv[1]);
/* If the output range was not large enough for all */
/* the information, only the data in the buffer is */
/* displayed */
if (count > BUFSIZE)
count = BUFSIZE;
for (i = 0; i < count; i++)
printinfo(&buf[i]);
return(0);
}
Can someone help me achieve the same in C#? And even explain why it have to be that way, so we all can learn something from this :)
Thanks!
The first problem I see that you use long type in C# structure declarations. Replace it with int - C# long has 64 bit length, and long in Win32 C has the same size as int - 32 bit.
Regarding ft_fileinfo *info parameter: for every non-trivial parameter which cannot be handled by default marshaller, use low level Marshal class functions. Function requires info to point to unmanaged memory block with size sizeof(fileinfo) * bufsize. Define this parameter as IntPtr:
[DllImport("ftapi.dll", EntryPoint = "ft_showdir")]
private static extern long ft_showdir(ft_admission admis, ref ft_shwpar par, IntPtr buf, int bufsize, ref ft_err errorinfo, ft_options options);
Allocate unmanaged memory block:
IntPtr f = Marshal.AllocHGlobal(Marshal.Sizeof(typeof(ft_fileinfo)) * bufsize);
Pass this parameter to unmanaged function. After function returns, read fileinfo structures using Marshal.PtrToStructure Method bufsize times, incrementing IntPtr by SizeOf(ft_fileinfo). Don't forget to release unmanaged memory block using Marshal.FreeHGlobal.
You can see, Marshal class allows to write interoperability code on C language level.
It looks like you are missing a ref before ft_fileinfo in your DllImport attribute. I reproduced your issue with the following cutdown. In C++ to define the entry point:
// OneEntryPoint.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include <stdio.h>
extern "C"
{
struct AStruct
{
char fn[11];
};
__declspec(dllexport) void AnEntry(AStruct* someStruct)
{
printf("A character %c", someStruct->fn[255]);
}
}
and in C# to reproduce the call:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication2
{
public struct AStruct
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
public string fn;
}
class Program
{
[DllImport("OneEntryPoint", CallingConvention = CallingConvention.Cdecl)]
static extern void AnEntry(AStruct somestruct);
static void Main(string[] args)
{
try
{
AStruct fred;
fred.fn = "hello world";
Program.AnEntry(fred);
Console.WriteLine("Done");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Run this and you get an AccessViolationException (attempt to read\write protected memory). Put the ref in and the struct is correctly marshalled and there are no errors.
Ok, I figured it out.
When looking at the c declaration of the function we can see that all of the parameters are sent in by reference or via pointers. So I just altered the pinvoke declaration so that all parameters where ref.
[DllImport("ftapi.dll", EntryPoint = "ft_showdir", CallingConvention = CallingConvention.Cdecl)]
private static extern Int32 ft_showdir(ref ft_admission admis, ref ft_shwpar par, ref ft_fileinfo[] buf, int bufsize, ref ft_err errorinfo, ref ft_options options);