StringBuilder Field in a Structure Cannot Be Marshaled Properly - 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);

Related

How to marshal c++ struct in c#

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.

C# calling a DLL function that returns a pointer to pointer to an array of structures

I have tried many different combinations of the various methods to marshal this call. This is a DLL that returns a pointer to a pointer to an array of structures. The types like debugPort are actually enums.
/**
* \struct debugConnectParameters
* \brief Get device characterization and specify connection parameters through ST-LINK interface.
*/
typedef struct debugConnectParameters {
debugPort dbgPort; /**< Select the type of debug interface #debugPort. */
int index; /**< Select one of the debug ports connected. */
char serialNumber[33]; /**< ST-LINK serial number. */
char firmwareVersion[20]; /**< Firmware version. */
char targetVoltage[5]; /**< Operate voltage. */
int accessPortNumber; /**< Number of available access port. */
int accessPort; /**< Select access port controller. */
debugConnectMode connectionMode; /**< Select the debug CONNECT mode #debugConnectMode. */
debugResetMode resetMode; /**< Select the debug RESET mode #debugResetMode. */
int isOldFirmware; /**< Check Old ST-LINK firmware version. */
frequencies freq; /**< Supported frequencies #frequencies. */
int frequency; /**< Select specific frequency. */
int isBridge; /**< Indicates if it's Bridge device or not. */
int shared; /**< Select connection type, if it's shared, use ST-LINK Server. */
} debugConnectParameters;
int getStLinkList(debugConnectParameters** stLinkList, int shared);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public unsafe struct debugConnectParameters
{
debugPort dbgPort; /**< Select the type of debug interface #debugPort. */
int index; /**< Select one of the debug ports connected. */
fixed char serialNumber[33]; /**< ST-LINK serial number. */
fixed char firmwareVersion[20]; /**< Firmware version. */
fixed char targetVoltage[5]; /**< Operate voltage. */
int accessPortNumber; /**< Number of available access port. */
int accessPort; /**< Select access port controller. */
debugConnectMode connectionMode; /**< Select the debug CONNECT mode #debugConnectMode. */
debugResetMode resetMode; /**< Select the debug RESET mode #debugResetMode. */
int isOldFirmware; /**< Check Old ST-LINK firmware version. */
frequencies freq; /**< Supported frequencies #frequencies. */
int frequency; /**< Select specific frequency. */
int isBridge; /**< Indicates if it's Bridge device or not. */
int shared; /**< Select connection type, if it's shared, use ST-LINK Server. */
}
[DllImport(dllPath + "CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public unsafe extern static int getStLinkList(
debugConnectParameters** stLinkList,
int shared
);
I have tried replacing the using "out" and "ref" options. I have tried to find a version of IntPtr that it likes. I keep getting: System.AccessViolationException. I have contacted STMicroelectronics about how to interface with this DLL, but so far they have been no help. The rest of the calls in the DLL are pretty simple but I must have this one working to get started, since it is the information about what JTAG device is actually connected.
Updated (6/12/20) to add the actual calling function and now the calling function copies the structures from unmanaged to managed structures. But the call to getStLinkList() still does not work.
public unsafe static int GetDeviceList(ref debugConnectParameters[] debugParams, int maxCount)
{
IntPtr unManagedListPtr = Marshal.AllocHGlobal(sizeof(debugConnectParameters*));
IntPtr *unManagedListPtrPtr = &unManagedListPtr;
int count = getStLinkList((debugConnectParameters**)unManagedListPtrPtr, 0);
IntPtr copyPtr = unManagedListPtr;
if (count > maxCount) count = maxCount;
for (int i = 0; i < count; i++)
{
debugParams[i] = (debugConnectParameters)Marshal.PtrToStructure(copyPtr, typeof(debugConnectParameters));
copyPtr += sizeof(debugConnectParameters);
}
Marshal.FreeHGlobal(unManagedListPtr);
return (count);
}
I also tried these changes with no luck. I am still seeing access violations. Attempt to read or write protected memory.
[DllImport(dllPath + "CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
unsafe private static extern int getStLinkList(
ref IntPtr stLinkList,
int shared
);
unsafe public static int GetDeviceList(List<debugConnectParameters> list)
{
IntPtr unManagedListPtr = Marshal.AllocHGlobal(sizeof(debugConnectParameters*));
IntPtr copyPtr = unManagedListPtr;
int count = getStLinkList(ref unManagedListPtr, 0);
if (count > 10) count = 10;
for (int i = 0; i < count; i++)
{
debugConnectParameters parameter = (debugConnectParameters)Marshal.PtrToStructure(copyPtr, typeof(debugConnectParameters));
list.Add(parameter);
copyPtr += sizeof(debugConnectParameters);
}
Marshal.FreeHGlobal(unManagedListPtr);
return (count);
}
There are a two things working against us in regards to calling the getStLinkList function of the "CubeProgrammer_API.dll" library in particular. Without correcting them the library will not work, even from C++.
First, the dependencies for that library found in the api/lib folder of the STM32CubeProgrammer installation are not all compiled for x64. The versions in the bin folder are and those are the libraries you'll want to have available at runtime. I've done this by setting the working directory to that directory in the project Debug settings. This was verified by checking the target machine of each DLL in the api/lib and bin folders.
Second, the "CubeProgrammer_API.dll" needs to be initialized before calling getStLinkList by calling the setLoadersPath and setDisplayCallbacks functions. The first function must be provided the path to the "Flash Loader", which is bin/FlashLoader in my case. The setDisplayCallbacks function takes a structure of three function pointers.
The example below might not be the best example of type marshaling, but it will illustrate the process.
public static class CubeProgrammerApi
{
public enum DebugConnectionMode
{
NormalMode = 0,
HotplugMode = 1,
UnderResetMode = 2,
PreResetMode = 3
}
public enum DebugResetMode
{
SoftwareReset = 0,
HardwareReset = 1,
CoreReset = 2
}
[StructLayout(LayoutKind.Sequential)]
public class Frequencies
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
uint[] JtagFrequency;
uint JTagFrequencyNumber;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
uint[] SwdFrequency;
uint SwdFrequencyNumber;
}
[StructLayout(LayoutKind.Sequential)]
public class DebugConnectParameters
{
DebugPort DebugPort;
public int Index;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
public string SerialNumber;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string FirmwareVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
public string TargetVoltage;
public int AccesPortNumber;
public int AccessPort;
public DebugConnectionMode connectionMode;
public DebugResetMode resetMode;
public bool IsOldFirmware;
public Frequencies Freqencies;
public int Frequency;
public bool IsBridge;
public int Shared;
}
public delegate void LogMessageReceived(int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message);
public delegate void InitProgressBar();
public delegate void ProgressBarUpdateReceived(int currentProgress, int total);
internal static class NativeMethods
{
public struct DisplayCallbacks
{
public InitProgressBar initProgressBar;
public LogMessageReceived logMessage;
public ProgressBarUpdateReceived loadBar;
}
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "setLoadersPath")]
internal static extern void SetLoadersPath(string path);
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "setDisplayCallbacks")]
internal static extern void SetDisplayCallbacks(ref DisplayCallbacks callbacks);
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "getStLinkList")]
internal static extern int GetStLinkList(IntPtr stLinkList, uint shared);
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void deleteInterfaceList();
[DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "reset")]
internal static extern int Reset([MarshalAs(UnmanagedType.U4)] DebugResetMode rstMode);
}
public static void SetLoadersPath(string path)
{
NativeMethods.SetLoadersPath(path);
}
public static void SetDisplayCallbacks(InitProgressBar initProgressBar, LogMessageReceived messageReceived, ProgressBarUpdateReceived progressBarUpdate)
{
NativeMethods.DisplayCallbacks callbacksHandle;
callbacksHandle.initProgressBar = initProgressBar;
callbacksHandle.logMessage = messageReceived;
callbacksHandle.loadBar = progressBarUpdate;
NativeMethods.SetDisplayCallbacks(ref callbacksHandle);
}
public static IList<DebugConnectParameters> GetStLinkProgrammers(bool shared = false)
{
var listPtr = Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>());
var parametersList = new List<DebugConnectParameters>();
try
{
var size = Marshal.SizeOf<DebugConnectParameters>();
var numberOfItems = NativeMethods.GetStLinkList(listPtr, shared ? 1U : 0U);
var listDereference = Marshal.PtrToStructure<IntPtr>(listPtr);
for (var i = 0; i < numberOfItems; i++)
{
var currentItem = Marshal.PtrToStructure<DebugConnectParameters>(listDereference + (i * size));
parametersList.Add(currentItem);
}
NativeMethods.deleteInterfaceList();
}
finally
{
Marshal.FreeHGlobal(listPtr);
}
return parametersList;
}
public static void FreeStLinkProgrammers()
{
NativeMethods.deleteInterfaceList();
}
}
And main:
class Program
{
static void Main(string[] args)
{
CubeProgrammerApi.SetLoadersPath(#"C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\FlashLoader");
CubeProgrammerApi.SetDisplayCallbacks(InitProgressBar, ReceiveMessage, ProgressBarUpdate);
var stLinkList = CubeProgrammerApi.GetStLinkProgrammers();
foreach (var stlink in stLinkList)
{
Console.WriteLine("{0} {1}", stlink.Index, stlink.SerialNumber);
}
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
CubeProgrammerApi.FreeStLinkProgrammers();
}
static void ReceiveMessage(int messgaeType, string message)
{
}
static void InitProgressBar()
{
}
static void ProgressBarUpdate(int currentProgress, int total)
{
}
}
This will output the following with two debuggers attached, a STLink V3 and an STLink V2 respectively. This should get you far enough along to discover the correct types to use.
0 002F001D3038511834333935
1 34FF6D065250343847110943
Press ENTER to exit

C# P/Invoke 2 dimensional array

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#

How to use a DLL and a .cs file in Delphi

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.

Invalid Managed/Unmanaged Type Combination With Embedded, Dynamically-Allocated Array

I have a common construct in an unmanaged Win32 C++ DLL:
// FirstElemPtrContainer.h
#include "stdafx.h"
typedef unsigned char elem_type; // a byte
typedef struct FirstElemPtrContainer {
unsigned char num_elems;
void *allocd_ary;
} FirstElemPtrContainer;
The void* in the struct is meant to contain a pointer to the first element of an allocated byte array.
The DLL that uses this definition then exports functions to allocate, populate, and deallocate the struct:
// The exported allocator function.
extern "C" _declspec(dllexport)
FirstElemPtrContainer *BuildStruct(int elem_count)
{
FirstElemPtrContainer *fepc_ptr = new FirstElemPtrContainer;
fepc_ptr->num_elems = elem_count;
elem_type *ary = new elem_type[fepc_ptr->num_elems];
for (int i = 0; i < fepc_ptr->num_elems; i++)
{
ary[i] = ((i + 1) * 5); // multiples of 5
}
fepc_ptr->allocd_ary = ary;
return fepc_ptr;
}
// The exported deallocator function.
extern "C" _declspec(dllexport) void
DestroyStruct(FirstElemPtrContainer *fepc_ptr)
{
delete[] fepc_ptr->allocd_ary;
delete fepc_ptr;
}
These work just fine for a native caller.
In C#, I try to describe this same structure via PInvoke:
[StructLayout(LayoutKind.Sequential)]
public struct FirstElemPtrContainer
{
public byte num_elems;
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.U1, SizeConst = 4)]
public IntPtr allocd_ary;
}
... and describe the call interface like so:
public static class Imports
{
[DllImport("MyLib", CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr BuildStruct(int elem_count);
[DllImport("MyLib", CallingConvention = CallingConvention.Winapi)]
public static extern void DestroyStruct(IntPtr fepc_ptr);
}
Now I attempt to call my interface:
class Program
{
const int NUM_ELEMS = 4;
static void Main(string[] args)
{
IntPtr fepc_ptr = Imports.BuildStruct(NUM_ELEMS);
if ( fepc_ptr == IntPtr.Zero )
{
Console.WriteLine("Error getting struct from PInvoke.");
return;
}
FirstElemPtrContainer fepc =
(FirstElemPtrContainer)Marshal.PtrToStructure(fepc_ptr,
typeof(FirstElemPtrContainer));
//...
}
}
The PtrToStructure() call gives the error "Cannot marshal field 'allocd_ary' of type 'MyLibInvoke.FirstElemPtrContainer': Invalid managed/unmanaged type combination (Int/UInt must be paired with SysInt or SysUInt)."
You can see that I've hard-coded a particular number of elements, which we'll assume the caller adheres to. I've also added an ArraySubType clause, though it seems not to make a difference. Why the type mismatch complaint?
Your struct should be declared like this:
[StructLayout(LayoutKind.Sequential)]
public struct FirstElemPtrContainer
{
public byte num_elems;
public IntPtr allocd_ary;
}
it has to be done this way because allocd_ary is a pointer to unmanaged memory and cannot be marshalled by the p/invoke marshaller.
In order to read the contents of allocd_ary you can use Marshal.Copy.
FirstElemPtrContainer fepc = (FirstElemPtrContainer)Marshal.
PtrToStructure(fepc_ptr, typeof(FirstElemPtrContainer));
byte[] ary = new byte[fepc.num_elems];
Marshal.Copy(fepc.allocd_ary, ary, 0, ary.Length);
I suspect that CallingConvention.Winapi is wrong and that you should be using CallingConvention.Cdecl.

Categories

Resources