integer handle to memory address C# - c#

My main purpose in doing this conversion is to create an object in C# based off of a memory address...would it be too hack-ish (or totally incorrect/stupid)? If so, is there a better way in doing this?
Something like this:
int app_handle = 920663024; // corresponds to memory location 0x36E033F0
string app_handle_converted_to_hex = decValue.ToString("X");
MyAppClass *newApp = (MyAppClass *)app_handle_converted_to_hex;
Also, is this possible to do at all without the use of pointers?

You're going to want to use Marshal.PtrToStructure which assumes a sequential layout.
Take a look at the example at the bottom of the page.
This code assumes 32-bit compilation. Before using a 64-bit compiler, replace IntPtr.ToInt32 with IntPtr.ToInt64.
[StructLayout(LayoutKind.Sequential)]
public class INNER
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
public string field1 = "Test";
}
[StructLayout(LayoutKind.Sequential)]
public struct OUTER
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
public string field1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public byte[] inner;
}
[DllImport(#"SomeTestDLL.dll")]
public static extern void CallTest( ref OUTER po);
static void Main(string[] args)
{
OUTER ed = new OUTER();
INNER[] inn = new INNER[10];
INNER test = new INNER();
int iStructSize = Marshal.SizeOf(test);
int sz =inn.Length * iStructSize;
ed.inner = new byte[sz];
try
{
CallTest( ref ed);
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
IntPtr buffer = Marshal.AllocCoTaskMem(iStructSize*10);
Marshal.Copy(ed.inner,0,buffer,iStructSize*10);
int iCurOffset = 0;
for(int i = 0; i < 10; i++)
{
inn[i] = (INNER)Marshal.PtrToStructure(new IntPtr(buffer.ToInt32() + iCurOffset),typeof(INNER) );
iCurOffset += iStructSize;
}
Console.WriteLine(ed.field1);
Marshal.FreeCoTaskMem(buffer);
}

I was able to figure it out based off of existing code in my application (many thanks to Romoku for mentioning Marshal)
My completed code looks like:
int handle = 920663024; // integer pointer corresponding to memory location 0x36E033F0
IntPtr app_handle = helper.GetAppPtr(handle) // gets IntPtr based off of handle
object obj = Marshal.GetObjectForIUnknown(app_handle);
MyAppClass newApp = obj as MyAppClass;
Works like a charm!

Related

faster way to return data as an array interoping c++

this is a very clean and nice solution to marsahall a struct array from unmanaged C++ code.
it is allmost perfect solution when it comes to simplicity, it took me a while to get to that level of understanding the concept, so that in few lines of code, as you can see C# Main(), i have a populated array of struct ready to be 'harvested'..
typedef struct {
int Id;
BSTR StrVal;
}Package;
extern "C" __declspec(dllexport) void dodata(int requestedLength,int StringSize, Package **Packs){
int count;
count=0;
*Packs = (Package*)LocalAlloc(0, requestedLength * sizeof(Package));
Package *Cur = *Packs;
while(count!= requestedLength)
{
Cur[count].StrVal = NULL;
Cur[count].Id = count;
Cur[count].StrVal=SysAllocString(L"abcdefghij");
Cur[count].StrVal[StringSize-1]=count+'0';
++count;
}
}
C#
[DllImport(#"ExportStructArr.dll", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern void dodata(int requestedLength, int StringSize, out IntPtr csPkPtr);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct csPk
{
public int V;
[MarshalAsAttribute(UnmanagedType.BStr, SizeConst = 10)]
public string testStr;
}
static void Main(string[] args){
int ArrL = 16000;
csPk[] Cpk = new csPk[ArrL];
IntPtr CpkPtr = IntPtr.Zero;
int szPk = Marshal.SizeOf(typeof(csPk));
dodata(ArrL, 10, out CpkPtr);
}
now all i have to do is :
for (int i = 0; i < Cpk.Length; i++)
{
Cpk[i] = (csPk)Marshal.PtrToStructure(new IntPtr(CpkPtr.ToInt32() + (szPk * i)), typeof(csPk));
}
the solution is quite easy as you can see
the question is using unsafe or any kind of transformation to the data, even going down to bytes...
how could i optimize it to perform better returning the data ?
Edit:
links i have tried to learn from other answers here in SO:
answer from Hans Passant
answer from AbdElRaheim
answer from supercat
also tried google : wikipedia , a github post by stephentoub
this is a complete blazing fast solution to populate a list of objects, i did my best
and i will be happy to have comments and suggestions.
c++
typedef struct _DataPacket
{
BSTR buffer;
UINT size;
} DataPacket;
extern "C" __declspec(dllexport) void GetPacksUnsafe( int size, DataPacket** DpArray )
{
int szr = size;int count=0;
*DpArray = (DataPacket*)CoTaskMemAlloc( szr * sizeof( DataPacket ));
if ( DpArray != NULL )
{
DataPacket* CurPack = *DpArray;
for ( int i = 0; i < szr; i++, CurPack++ )
{
CurPack->size = i;
CurPack->buffer = SysAllocString(L"SomeText00");
CurPack->buffer[9]=i+'0';
}
}
}
C#
[DllImport(#"ExportStructArr.dll", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern void GetPacksUnsafe(int size, PackU** outPackUArr);
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PackU
{
public char* StrVal;
public int IntVal;
}
public static unsafe List<PackU> PopulateLstPackU(int ArrL)
{
PackU* PackUArrOut;
List<PackU> RtLstPackU = new List<PackU>(ArrL);
GetPacksUnsafe(ArrL, &PackUArrOut);
PackU* CurrentPack = PackUArrOut;
for (int i = 0; i < ArrL; i++, CurrentPack++)
{
RtLstPackU.Add(new PackU(){ StrVal = CurrentPack->StrVal, IntVal=CurrentPack->IntVal});
}
Marshal.FreeCoTaskMem((IntPtr)PackUArrOut);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("{0}", new string(RtLstPackU[i].StrVal));
}
return RtLstPackU;
}
using the code is as simple as it could possibly be
static unsafe void Main(string[] args)
{
int ArrL = 100000;
List<PackU> LstPackU;
LstPackU = PopulateLstPackU(ArrL);
}
there you have a list of custom data as fast as a bullet..
EDIT
using pointers instead of strings :
typedef struct _DataPackCharPnt
{
char* buffer;
UINT IntVal;
} DataPackCharPnt;
extern "C" __declspec(dllexport) void GetPacksPnt( int size, DataPackCharPnt** DpArrPnt )
{
int count = 0;
int TmpStrSize = 10;
*DpArrPnt = (DataPackCharPnt*)CoTaskMemAlloc( size * sizeof( DataPackCharPnt ));
DataPackCharPnt* CurPackPnt = *DpArrPnt;
char dummyStringDataObject[]= "abcdefgHi";
for ( int i = 0; i < size; i++,CurPackPnt++ )
{
dummyStringDataObject[9] = i+'0';
CurPackPnt->IntVal=i;
CurPackPnt->buffer = (char*)malloc(sizeof(char)*TmpStrSize);
strcpy(CurPackPnt->buffer, dummyStringDataObject);
}
}
reduced the time taken from 11 to 7 ms populating 100k elements
is there any part of creating the buffer i could omit ?
the duty of dummyStringDataObject is to simulate work, say getting a file name then set the buffer with its value, so except for this extra time which is the whole purpose of this function, to return some unknown values and lengths of the strings...
could you optimize it even further ?

How to assign (byte* + offset) to byte[]?

I have a struct:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct FieldIndex {
public fixed byte Meta[16];
}
The first byte is TypeCode. The remaining 15 bytes is a UTF8-encoded string.
I'm using the following to fill the nameBuf byte array:
private static string GetMetaName (FieldIndex meta) {
var idx = 0;
var bytesOut = 1;
var nameBuf = new byte[Indexer.MAXFIELDNAMELENGTH]; // 15
while (bytesOut < Indexer.MAXFIELDLENGTH) { // 16
nameBuf[idx] = *(byte*)((byte*)&meta + bytesOut);
++bytesOut;
++idx;
}
//var src = (byte*)&field.meta + 1;
//fixed (byte* b = nameBuf) {
// *((byte*)b) = (byte)src;
//}
return BinaryConverter.ToString(nameBuf, 0, Indexer.MAXFIELDNAMELENGTH, Indexer.DefaultEncoding);
}
In the commented code above, I was trying to accomplish the same task without the while iteration but it does not work (no compile error, just the wrong interpolation). Can I assign nameBuf without the while-loop?
Update
I'd also prefer using the (byte*) rather than Marshal.Copy.
You could try to use the Marshal.Copy static method in unsafe context:
unsafe
{
Marshal.Copy(new IntPtr(meta), nameBuf, 1, nameBuf.Length);
}
There isn't much point in doing this the hard unsafe way when you can do it just easily this way:
[StructLayout(LayoutKind.Sequential)]
struct FieldIndex {
public byte Typecode;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
private byte[] Utf8string;
public string GetString() {
return Encoding.UTF8.GetString(Utf8string, 0, 15);
}
}
No while-loop, mission accomplished. That pointer is still there and is just as efficient, you just don't see it anymore. If you fret over calling the GetString() method too often then simply declare another public class and keep this one internal.

How to do memcpy in C# .Net CF with the following task

Hi I am trying to convert the C/C++ Strcut to C# and how to fill the structure member with address of another structure in C#?
C/C++ Struct looks like:
typedef struct _NDISUIO_QUERY_OID
{
NDIS_OID Oid;
PTCHAR ptcDeviceName;
UCHAR Data[sizeof(ULONG)];
} NDISUIO_QUERY_OID, *PNDISUIO_QUERY_OID;
typedef struct My_Struct
{
//les have 2 variables...
UINT a;
UINT b;
}My_STATS, *PMy_STATS;
PNDISUIO_QUERY_OID pQueryOid = NULL;
pQueryOid = (PNDISUIO_QUERY_OID)malloc(sizeof(NDISUIO_QUERY_OID)+ sizeof(My_STATS)) ;
PMy_STATS Statistics;
pQueryOid->Oid = ulOIDCode;//Required OID
pQueryOid->ptcDeviceName = AUB_NAME;//REquired STRING
memcpy(pQueryOid->Data, Statistics, sizeof(My_STATS));
My C# Struct is:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _NDISUIO_QUERY_OID
{
public uint Oid;
[MarshalAs(UnmanagedType.LPWStr)]
public string ptcDeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = sizeof(uint))]
public string Data;
};
Problem: How to copy the Statistics structure to Data array in C#??
Thanks :)
Here's my implementation (FYI, the SDF contains all of this code and a lot more)
internal class NDISQueryOid
{
protected const int NDISUIO_QUERY_OID_SIZE = 12;
protected byte[] m_data;
public int Size { get; private set; }
public NDISQueryOid(byte[] data)
{
int extrasize = data.Length;
Size = 8 + extrasize;
m_data = new byte[Size];
Buffer.BlockCopy(data, 0, m_data, DataOffset, data.Length);
}
public NDISQueryOid(int extrasize)
{
Size = NDISUIO_QUERY_OID_SIZE + extrasize;
m_data = new byte[Size];
}
protected const int OidOffset = 0;
public uint Oid
{
get { return BitConverter.ToUInt32(m_data, OidOffset); }
set
{
byte[] bytes = BitConverter.GetBytes(value);
Buffer.BlockCopy(bytes, 0, m_data, OidOffset, 4);
}
}
protected const int ptcDeviceNameOffset = OidOffset + 4;
public unsafe byte* ptcDeviceName
{
get
{
return (byte*)BitConverter.ToUInt32(m_data, ptcDeviceNameOffset);
}
set
{
byte[] bytes = BitConverter.GetBytes((UInt32)value);
Buffer.BlockCopy(bytes, 0, m_data, ptcDeviceNameOffset, 4);
}
}
protected const int DataOffset = ptcDeviceNameOffset + 4;
public byte[] Data
{
get
{
byte[] b = new byte[Size - DataOffset];
Array.Copy(m_data, DataOffset, b, 0, Size - DataOffset);
return b;
}
set
{
Size = 8 + value.Length;
m_data = new byte[Size];
Buffer.BlockCopy(value, 0, m_data, DataOffset, value.Length);
}
}
public byte[] getBytes()
{
return m_data;
}
public static implicit operator byte[](NDISQueryOid qoid)
{
return qoid.m_data;
}
}
Note that in my usage, the NDIS IOCT takes in a pointer (most of my NDIS work is all done as unsafe) so you'd have to do some adjustment there.
So if, for example, you're querying the BSSID, I know the BSSID data is 36 bytes, so I'd create something like this:
var queryOID = new NDISQueryOid(36);
then allocate the name and call NDIS (the production code has a lot more checking than this):
byte[] nameBytes = System.Text.Encoding.Unicode.GetBytes(adapterName + '\0');
fixed (byte* pName = &nameBytes[0])
{
queryOID.ptcDeviceName = pName;
queryOID.Oid = (uint)oid;
var bytes = queryOID.getBytes();
ndis.DeviceIoControl(IOCTL_NDISUIO_QUERY_OID_VALUE, bytes, bytes);
var result = new byte[queryOID.Data.Length];
Buffer.BlockCopy(queryOID.Data, 0, result, 0, result.Length);
}
EDIT
So the result member above is a byte array of the "result" of the query. What it means and how you interpret it depends on what the OID you queried was. For example, if you were querying the currently connected SSID (i.e. NDIS_OID.SSID), then that comes back as a 4-byte length followed by the ASCII-encoded name, so you'd decipher it like this:
int len = BitConverter.ToInt32(data, 0);
if (len > 0)
{
ssid = System.Text.Encoding.ASCII.GetString(data, 4, len);
}
But again, this is only for one specific OID. You have to handle every return case for every incoming OID you decide to support.
First you have the wrong translation of your C++ code: the C# equivalent of a C++ char[] is not a string, it's a byte[]. Once you have that, you just need to know, in general, how to copy a structure into a byte array. Here's a compilable example:
using System;
using System.Runtime.InteropServices;
struct Dest
{
public byte[] Data;
}
struct Src
{
public GCHandle StringHandle;
public long A;
public long B;
}
class Program
{
static void Main()
{
Copy();
}
static void Copy()
{
var str = "Hello";
var src = new Src {
A = 3,
B = 4,
StringHandle = GCHandle.Alloc(str, GCHandleType.Normal)
};
var dst = new Dest();
unsafe
{
Src* srcPtr = &src;
dst.Data = new byte[sizeof(Src)];
Marshal.Copy((IntPtr)srcPtr, dst.Data, 0, sizeof(Src));
}
// When you're sure no one can reference the string anymore
// (Including by accessing the data you put in dst.Data!)
src.StringHandle.Free();
}
EDIT: added example of how to deal with reference types such as strings.
Safely, you can't. .NET enforces type safety, which means that you simply can't force a string to be a structure. However, you can look at the data instead of doing unsafe type casts (why are you storing two uints in a string in the first place? And marshalling it as unicode?
First, you'll have to make Data a byte array. It might be possible to do this with a string as well, but that's just adding encoding issues to the mix; if you can, use byte[] instead. Also, if you don't need to have different kinds of data inside (it seems so), you could simply put the two uint fields right inside the struct and it should work just fine:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _NDISUIO_QUERY_OID
{
public uint Oid;
[MarshalAs(UnmanagedType.LPWStr)]
public string ptcDeviceName;
public uint DataA;
public uint DataB;
};
The second approach would use a const-sized byte array, long enough to hold the two uints:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _NDISUIO_QUERY_OID
{
public uint Oid;
[MarshalAs(UnmanagedType.LPWStr)]
public string ptcDeviceName;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = sizeof(ulong))]
public byte[] Data;
};
The first four bytes will be the first uint, the next will be the second.
And of course, you could also use a .NET struct the same way as in the original code - just make sure you use the correct datatype in _NDISUIO_QUERY_OID and it should work automagically.
One point to note though, it seems that the data returned isn't actually necessarily fixed-length. That is quite tricky and it basically means you'd have to deserialize the structure manually based on the pointer and length you get.

FatalExecutionEngineError during C# Marshalling

I'm running into an issue when trying to read c++ structures from memofields in a number of DBase IV files into C# (.Net 4) and then insert them into MSSQL 2008. The data is being extracted ok from the DBase files but I seemed to be doing something wrong with managing the unsafe calls because I randomly get the following error:
FatalExecutionEngineError was detected
Message: The runtime has encountered a fatal error. The address of the error was
at 0x791fa62c, on thread 0x16c0. 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.
The following is the code I use to read the data. This code completes successfully and the data I get from the files are correct. The error occurs a number of minutes after this function is called, when the objects are being inserted into the database using nhibernate (I left it out because I didn't think it really matter to the issue).
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Event
{
public int Number;
public int Month;
public int Day;
public int Year;
public int Hour;
public int Minute;
public int Second;
public UInt32 UPCTime;
public int BlobSize;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = DataLengths.MAX_EVENT_TITLE_LENGTH)]
public string Title;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = DataLengths.MAX_TRIGGER_LENGTH)]
public string Trigger;
}
public struct Trigger
{
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
public string Control;
public int Index;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
public string Mode;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
public string RecordFreq;
public int Pre;
public int Post;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
public string Source;
public int Delay;
public int EventUserNotify;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
public int[] Spare;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = DataLengths.MAX_EVENT_SENSORS)]
public int[] Sensors;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Value
{
public Trigger Trigger;
public string[] SensorLabels;
public Point[] Point;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Point
{
public Single Value;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
public string State;
}
//The dbf is from the java xBasej library that I compiled into a dll using IKVM.net
public Dictionary<Event, Value> Read(DBF dbf)
{
Dictionary<Event, Value> eventData = new Dictionary<Event, Value>();
try
{
for (int i = 1; i <= dbf.getRecordCount(); i++)
{
dbf.gotoRecord(i);
MemoField memofield = (MemoField)dbf.getField("MemoField");
// Perform the conversion from one encoding to the other.
byte[] blob = memofield.getBytes();
if (blob.Length == 0)
{
continue;
}
MemoryStream memoryStream = null;
BinaryReader binaryReader = null;
GCHandle eventHandle = new GCHandle();
GCHandle triggerHandle = new GCHandle();
GCHandle sensorLabelHandle = new GCHandle();
GCHandle pointHandle = new GCHandle();
try
{
memoryStream = new MemoryStream(blob);
binaryReader = new BinaryReader(memoryStream);
//The data was orignally C++ structures so we read the bytes back into C# equivalent
//structures.
int eventDataSize = Marshal.SizeOf(typeof(Event));
eventHandle = GCHandle.Alloc(binaryReader.ReadBytes(eventDataSize), GCHandleType.Pinned);
Event #event = (Event)Marshal.PtrToStructure(eventHandle.AddrOfPinnedObject(), typeof(Event));
//Read the event trigger data
int triggerDataSize = Marshal.SizeOf(typeof(Trigger));
triggerHandle = GCHandle.Alloc(binaryReader.ReadBytes(triggerDataSize), GCHandleType.Pinned);
Trigger trigger = (Trigger)Marshal.PtrToStructure(triggerHandle.AddrOfPinnedObject(), typeof(Trigger));
Value value = new Value();
value.Trigger = trigger;
triggerHandle.Free();
//Read all the sensor labels
List<string> sensorLableList = new List<string>();
for (int sensorIndex = 0; sensorIndex < DataLengths.MAX_EVENT_SENSORS; sensorIndex++)
{
int sensorLableDataSize = DataLengths.MAX_LABEL_LENGTH;
sensorLabelHandle = GCHandle.Alloc(binaryReader.ReadBytes(sensorLableDataSize), GCHandleType.Pinned);
string label = Marshal.PtrToStringAnsi(sensorLabelHandle.AddrOfPinnedObject(), sensorLableDataSize).Trim();
sensorLableList.Add(label);
sensorLabelHandle.Free();
}
value.SensorLabels = sensorLableList.ToArray();
//Read all the recorded sensor data
List<Point> pointList = new List<Point>();
for (int pointIndex = 0; pointIndex < DataLengths.MAX_EVENT_SENSORS; pointIndex++)
{
int pointDataSize = Marshal.SizeOf(typeof(Point));
pointHandle = GCHandle.Alloc(binaryReader.ReadBytes(pointDataSize), GCHandleType.Pinned);
Point point = (Point)Marshal.PtrToStructure(pointHandle.AddrOfPinnedObject(), typeof(Point));
pointList.Add(point);
pointHandle.Free();
}
value.Point = pointList.ToArray();
eventData.Add(#event, value);
eventHandle.Free();
}
finally
{
//Free all the resources used to get the data
if (memoryStream != null) { memoryStream.Close(); }
if (binaryReader != null) { binaryReader.Close(); }
if (eventHandle.IsAllocated) { eventHandle.Free(); }
if (triggerHandle.IsAllocated) { triggerHandle.Free(); }
if (sensorLabelHandle.IsAllocated) { sensorLabelHandle.Free(); }
if (pointHandle.IsAllocated) { pointHandle.Free(); }
GC.Collect();
}
}
}
finally
{
if (dbf != null)
{
dbf.close();
}
}
return eventData;
}
Interesting failure mode, this is not supposed to happen. You get the FEEE typically because the garbage collected heap is getting destroyed. This is normally easily explained by a misbehaving pinvoked native function that does something like overflow a buffer. No pinvoke here though.
There's however an excellent candidate for this mishap:
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)]
public string Control;
The marshaling for this is designed to marshal a C string, a zero-terminated array of characters. A SizeConst = 1 cannot work properly by design, that only leaves room for the zero terminator, not any character. On top of which, a .dbf file doesn't contain zero terminated strings, they are fixed-width according to the field declaration.
Whether the access violation is actually caused by the marshaller getting this wrong is an open question though, it can just as easily bomb on trying to find the zero terminator. Not find one and blunder into an unmapped memory page.
Anyhoo, you're doing it wrong. You must use a char[] instead of a string in the structure declaration and use ByValArray in the [MarshalAs] attribute. Pretty painful coding btw.
Within your Trigger structure, are you sure you have the right packing? It looks like the Index member would be at offset 4 where as you might have intended it to be at offset 1? Same with Pre and Post.
Also, what size is Control supposed to be? If it is a string, usually unmanaged strings are null-terminated. By specifying the length of 1, that would indicate to me a single character. If that is the case, why not use char for it instead of string?
Error 0xc0000005 is an access violation.
Have you tried running under the debugger and enabling unmanaged code debugging? You can then set the access violation to be caught right when thrown as it might be happening inside .NET framework code. You then could look at memory and get an idea of what it might be doing.

Marshalling array of strings to char ** in C#

I'm calling a C DLL function and need to supply the following C struct:
typedef struct
{
char *mTableId;
char **mFieldNames;
int mNumFields;
char *mFilter;
char *mSort;
int mOffset;
int mMaxRecords;
char *mTargetRecordFilter;
int mSurroundingRecordsCount;
int *mOwnerIds;
int mNumOwnerIds;
gsi_bool mCacheFlag;
} SAKESearchForRecordsInput;
The problem is with char **mFieldNames; I've tried marshalling automatically like this:
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPTStr, SizeConst = 9)]
public String[] mFieldNames;
This way I get an error in Marshal.SizeOf() - can't compute the correct size. Then I decided to deal with pointers manually. It's in fact just a pointer to the array of C strings. Here's my code which is leading to
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
So I've screwed up pointers somewhere. The code seems OK to me, where is the bug?
C#:
[StructLayout(LayoutKind.Sequential)]
unsafe public class SAKESearchForRecordsInput {
[MarshalAs(UnmanagedType.LPTStr)]
public String mTableId;
//[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPTStr, SizeConst = 9)] // HARDCODED!?!
//public String[] mFieldNames; // char **mFieldNames;
public IntPtr mFieldNames;
public int mNumFields;
[MarshalAs(UnmanagedType.LPTStr)]
public String mFilter;
[MarshalAs(UnmanagedType.LPTStr)]
public String mSort;
public int mOffset;
public int mMaxRecords;
//[MarshalAs(UnmanagedType.LPTStr)]
public IntPtr mTargetRecordFilter;
public int mSurroundingRecordsCount;
public IntPtr mOwnerIds;
public int mNumOwnerIds;
public gsi_bool mCacheFlag;
}
[DllImport("saketestd.dll")]
unsafe static extern void* sakeSearchForRecords(
IntPtr sake,
IntPtr input, //SAKESearchForRecordsInput *
SAKERequestCallback callback, //SAKERequestCallback
IntPtr userData);
unsafe public bool sakeSearchForRecordsE() {
bool ret = false;
try {
searchInput.mTableId = "bbdx_score";
//searchInput.mFieldNames = mFieldNames.to;
searchInput.mFilter = "num_ratings = 0 AND filestore > 0";
searchInput.mSort = "";
searchInput.mOffset = 0;
searchInput.mMaxRecords = 1;
//searchInput.mTargetRecordFilter = "";
searchInput.mSurroundingRecordsCount = 0;
searchInput.mOwnerIds = IntPtr.Zero;
searchInput.mNumOwnerIds = 0;
searchInput.mCacheFlag = true;
int sakeSize = Marshal.SizeOf(sake);
debug.AddLine(this.getMethodName() + ": sizeof(sake): " + sakeSize);
IntPtr pSake = Marshal.AllocHGlobal(sakeSize);
Marshal.StructureToPtr(sake, pSake, true);
int inputSize = Marshal.SizeOf(searchInput);
debug.AddLine(this.getMethodName() + ": sizeof(input): " + inputSize);
IntPtr pInput = Marshal.AllocHGlobal(inputSize);
Marshal.StructureToPtr(searchInput, pInput, true);
IntPtr[] mFieldNamesPtr;
int i;
if (true) { // IntPtr[]
mFieldNamesPtr = new IntPtr[mFieldNames.Length];
i = 0;
foreach (string str in mFieldNames) {
mFieldNamesPtr[i++] = Marshal.StringToHGlobalAnsi(str);
}
//searchInput.mFieldNames = mFieldNamesPtr;
} else {
//searchInput.mFieldNames = mFieldNames;
}
searchInput.mNumFields = mFieldNames.Length;
void* pRequestInternal = null;
void* p = mFieldNamesPtr[0].ToPointer();
searchInput.mFieldNames = (IntPtr)p;
pRequestInternal = sakeSearchForRecords(
pSake,
pInput,
new SAKERequestCallback(this.sakeSearchForRecordsCB),
IntPtr.Zero
);
sake = (SAKEInternal)Marshal.PtrToStructure(pSake, typeof(SAKEInternal));
if (searchRequest == null) {
debug.AddLine(this.getMethodName() + ": mStartRequestResult: " + sake.mStartRequestResult);
} else {
ret = true;
this.searchRequest = (SAKERequestInternal)Marshal.PtrToStructure(
new IntPtr(pRequestInternal),
typeof(SAKERequestInternal)
);
searchInput = (SAKESearchForRecordsInput)Marshal.PtrToStructure(
pInput,
typeof(SAKESearchForRecordsInput)
);
if (true) {
i = 0;
foreach (string str in mFieldNames) {
Marshal.FreeHGlobal(mFieldNamesPtr[i++]);
}
}
PrintStruct ps = new PrintStruct(sake);
debug.AddLine(this.getMethodName() + ": sake: " + ps);
ps = new PrintStruct(searchRequest);
debug.AddLine(this.getMethodName() + ": searchRequest: " + ps.print_r());
ps = new PrintStruct(searchInput);
debug.AddLine(this.getMethodName() + ": searchInput: " + ps.print_r());
}
Marshal.FreeHGlobal(pSake);
Marshal.FreeHGlobal(pInput);
} catch (Exception ex) {
debug.Text += ex.ToString();
}
return ret;
}
The best way to Marshal nasty string pointers, especially double pointers within a struct is to simply use an IntPtr.
public IntPtr mFieldNames;
This will Marshal correctly albeit with a not so useful type. However if you understand the structure of the IntPtr it's very easy to get the resulting strings out.
public static List<string> GetAllStrings(IntPtr ptr, int size) {
var list = new List<string>();
for ( int i = 0; i < size; i++ ) {
var strPtr = (IntPtr)Marshal.PtrToStructure(ptr, typeof(IntPtr));
list.Add(Marshal.PtrToStringUni(strPtr));
ptr = new IntPtr(ptr.ToInt64()+IntPtr.Size);
}
return list;
}
The only real downside is that you will have to manually free the memory
A better way is simply to use unsafe code with sbyte which is the same as c-char (-128 to 127) 1 byte.
You can write yourself some extern functions like alloc_txt, free_txt,etc.. for allocating and freeing from the heap. Mostly when I write with interop I do use unsafe code because IntPtr gets you the address but you still have to use extern functions to get members in the structure it points to or if a primitive have to Marshal methods to extract what the value is.
The only time you have to declare a c# structure as unsafe is if you are using actual pointers which you are not but using MarshalAs instead. I still would prefer you use unsafe pointers via MarshalAs(UnmanagedType.?) which allows you do deal with the members directly.
[Struct(Layout.Sequential)]
public unsafe struct SAKESearchForRecordsInput
{
sbyte*mTableId;
sbyte**mFieldNames;
int mNumFields;
sbyte*mFilter;
sbyte*mSort;
int mOffset;
int mMaxRecords;
char*mTargetRecordFilter;
int mSurroundingRecordsCount;
int*mOwnerIds;
int mNumOwnerIds;
bool mCacheFlag;//?don't know what the typedef for the bytes
};

Categories