What are some good strategies for working with DeviceIoControl? - c#

I am looking for some guidance when it comes to call DeviceIoControl from C#, knowing that its generic aspect of accepting pointer parameters isn't always easy to express in C#.
Here are two examples and explanations laid out below.
Example 1:
This works but is cumbersome, you have a disposable scope but you have to pass the parameters to the function and at the end assign the output buffer value back to the variable.
var toc = new CDROM_TOC(); // non blittable
var code = NativeConstants.IOCTL_CDROM_READ_TOC;
using (var scope = new UnmanagedMemoryScope<CDROM_TOC>(toc))
{
if (!UnsafeNativeMethods.DeviceIoControl(Handle, code, IntPtr.Zero, 0, scope.Memory, scope.Size, out _))
return Array.Empty<ITrack>();
toc = scope.Value; // this is weird
}
Example 1 helper:
internal struct UnmanagedMemoryScope<T> : IDisposable where T : struct
{
private bool IsDisposed { get; set; }
public uint Size { get; }
public IntPtr Memory { get; }
public T Value
{
get => Marshal.PtrToStructure<T>(Memory);
set => Marshal.StructureToPtr(value, Memory, true);
}
public UnmanagedMemoryScope(T value)
{
var size = Marshal.SizeOf<T>();
Memory = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(value, Memory, false);
Size = (uint)size;
IsDisposed = false;
}
public void Dispose()
{
if (IsDisposed)
return;
if (Memory != default)
Marshal.FreeHGlobal(Memory);
IsDisposed = true;
}
}
Example 2:
This one is already much more friendly, wrappers do marshalling and the value passed is ref.
var toc = new CDROM_TOC(); // non blittable
var code = NativeConstants.IOCTL_CDROM_READ_TOC;
var ioctl = DeviceIoControl(Handle, code, ref toc);
// ...
Example 2 helper 1:
private static bool DeviceIoControl<TTarget>(
SafeFileHandle handle, uint code, ref TTarget target)
where TTarget : struct
{
var sizeOf = Marshal.SizeOf<TTarget>();
var intPtr = Marshal.AllocHGlobal(sizeOf);
Marshal.StructureToPtr(target, intPtr, false);
var ioctl = UnsafeNativeMethods.DeviceIoControl(
handle,
code,
IntPtr.Zero,
0u,
intPtr,
(uint)sizeOf,
out var lpBytesReturned
);
target = Marshal.PtrToStructure<TTarget>(intPtr);
Marshal.FreeHGlobal(intPtr);
return ioctl;
}
Example 2 helper 2:
private static bool DeviceIoControl<TTarget, TSource>(
SafeFileHandle handle, uint code, ref TTarget target, ref TSource source)
where TSource : struct
where TTarget : struct
{
var sizeOf1 = Marshal.SizeOf(source);
var sizeOf2 = Marshal.SizeOf(target);
var intPtr1 = Marshal.AllocHGlobal(sizeOf1);
var intPtr2 = Marshal.AllocHGlobal(sizeOf2);
Marshal.StructureToPtr(source, intPtr1, false);
Marshal.StructureToPtr(target, intPtr2, false);
var ioctl = UnsafeNativeMethods.DeviceIoControl(
handle,
code,
intPtr1,
(uint)sizeOf1,
intPtr2,
(uint)sizeOf2,
out var lpBytesReturned
);
Marshal.PtrToStructure(intPtr1, source);
Marshal.PtrToStructure(intPtr2, target);
Marshal.FreeHGlobal(intPtr1);
Marshal.FreeHGlobal(intPtr2);
return ioctl;
}
But I feel that I might be missing something and maybe there's a better approach...
Question:
What are some good tricks when it comes to call DeviceIoControl from C#?
Knowing that,
want to avoid the use of unsafe keyword
there are non-blittable types so fixed is out of question for them
the function accepts arbitrary types, it's only buffers for it in the end
Of course there's the C++/CLI route but well, it's not C# anymore...
Hope that makes sense to you, else let me know.

I usually do it like this.
Parameters structure:
ref struct CDROM_TOC
{
const int MAXIMUM_NUMBER_TRACKS = 100;
public const int sizeInBytes = 4 + MAXIMUM_NUMBER_TRACKS * 8;
readonly Span<byte> buffer;
public CDROM_TOC( Span<byte> buffer )
{
if( buffer.Length != sizeInBytes )
throw new ArgumentException();
this.buffer = buffer;
}
/// <summary>Fixed header of the structure</summary>
public struct Header
{
public ushort length;
public byte firstTrack, lastTrack;
}
/// <summary>Fixed header</summary>
public ref Header header =>
ref MemoryMarshal.Cast<byte, Header>( buffer.Slice( 0, 4 ) )[ 0 ];
public struct TRACK_DATA
{
byte reserved;
public byte controlAndAdr;
public byte trackNumber;
byte reserved2;
public uint address;
}
/// <summary>Tracks collection</summary>
public Span<TRACK_DATA> tracks =>
MemoryMarshal.Cast<byte, TRACK_DATA>( buffer.Slice( 4 ) );
// Make this structure compatible with fixed() statement
public ref byte GetPinnableReference() => ref buffer[ 0 ];
}
Usage example:
CDROM_TOC toc = new CDROM_TOC( stackalloc byte[ CDROM_TOC.sizeInBytes ] );
unsafe
{
fixed( byte* buffer = toc )
{
// Here you have unmanaged pointer for that C interop.
}
}
// If you want to return the tracks, need to copy to managed heap:
var header = toc.header;
return toc.tracks
.Slice( header.firstTrack, header.lastTrack - header.firstTrack + 1 )
.ToArray();
Couple more notes.
The answer assumes you have a modern C#, i.e. .NET 5 or newer, or any version of .NET Core.
The example does use unsafe, but only on the lowest level. If you absolutely don’t want that, use GCHandle instead. With GCHandleType.Pinned, it’s an equivalent to unsafe keyword, only slower.
Unlike your code, this method does not use any heap memory for the interop, neither managed nor native.
The instance of the structure is stack allocated, and it exposes higher-level API to access the fields of that structure. The complete stack is already fixed in memory, the fixed keyword going to nothing for that code, just return the address. Doing nothing is free performance-wise.

Related

Having trouble serializing these values in C# to be properly read in C++ as a known struct

We've been given a program from another organization that collects data from multicast sources and collates and saves that data. It expects a C++ struct formatted as such:
#define SP_PACKET_SIZE 200
#define NAME_SIZE 64
struct spPacketStruct
{
int Size;
char Name[SP_PACKET_SIZE][NAME_SIZE];
double Value[SP_PACKET_SIZE];
};
obviously I can't use this struct in C# because a struct can't have preinitialized arrays, so I figured create individual bits and just serialize them. So now I have this in C#:
int SpPacketSize;
char[,] SpNames = new char[SP_PACKET_SIZE, NAME_SIZE];
double[] SpValues = new double[SP_PACKET_SIZE];
My previous experience is with a BinaryWriter...I don't need to deserialize in C#, I just need to get it to the C++ program. My test serialization code is as follows:
System.IO.MemoryStream outputstream = new System.IO.MemoryStream();
BinaryFormatter serializer = new BinaryFormatter();
serializer.TypeFormat = System.Runtime.Serialization.Formatters.FormatterTypeStyle.TypesWhenNeeded;
serializer.Serialize(outputstream, SpPacketSize);
serializer.Serialize(outputstream, SpNames);
serializer.Serialize(outputstream, SpValues);
byte[] buffer = outputstream.GetBuffer();
udpclient.Send(buffer, buffer.Length, remoteep);
And I get a binary packet but the length isn't right because it's still including the type formats. When I look at this packet in Wireshark I see a System.Int32 notation in the beginning of it. This is making the packet larger than expected and thus not deserialized properly on the C++ side.
I added the TypesWhenNeeded TypeFormat thinking I could minimize it, but it didn't change...and I noticed there was no option to not TypeFormat, unless I missed it somewhere.
Does anyone have any hints on how to properly serialize this data without the extra info?
While you cannot pre-initialize struct fields, nor directly put the size in MarshalAs attribute for ByValue 2D array, there is a little work-around you can do. You can define two structs like this:
const short SP_PACKET_SIZE = 200;
const short NAME_SIZE = 64;
struct spPacketStruct
{
public int Size;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SP_PACKET_SIZE)]
private fixedString[] names;
public fixedString[] Names { get { return names ?? (names = new fixedString[SP_PACKET_SIZE]); } }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SP_PACKET_SIZE)]
private double[] value;
public double[] Value { get { return value ?? (value = new double[SP_PACKET_SIZE]); } }
}
struct fixedString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAME_SIZE)]
public string Name;
}
By having additional struct to be a member of the original struct, you are able to specify the length of both dimensions by setting SizeConst in the original struct to the first dimension and setting it to the second dimension in the new struct. Making the field private and creating properties for them is merely for convenience, so you don't have to assign the array yourself when creating the struct.
Then you can serialize/deserialize the struct like this (code from this answer: https://stackoverflow.com/a/35717498/9748260):
public static byte[] GetBytes<T>(T str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
GCHandle h = default;
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
Marshal.StructureToPtr(str, h.AddrOfPinnedObject(), false);
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return arr;
}
public static T FromBytes<T>(byte[] arr) where T : struct
{
T str = default;
GCHandle h = default;
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return str;
}
And one last thing when trying to serialize/deserialize structs like this, be aware of the struct alignment as it can mess with the struct size

Why does my returned object not match my received object?

I am trying to deserialize a byte array to a struct.
Here is my deserialization function:
void RawDeserialize(byte[] bytearray, object obj)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray, 0, i, len);
obj = Marshal.PtrToStructure(i, obj.GetType());
Marshal.FreeHGlobal(i);
}
I call it with
RawDeserialize(outarr, outbuf);
Where outarr is a byte array of length 22 and outbuf is my struct which looks like this:
[StructLayout(LayoutKind.Sequential,Size =22)]
public struct ID_OUTPUT
{
public HEADER_OUTPUT hdr; //Another struct size=8
public byte bType;
public byte bRunning;
[MarshalAs(UnmanagedType.ByValTStr,SizeConst = 8)]
public string softwareName;
public short softwareVersion;
}
When I step-through debug in my deserialization function, obj is filled with correct values, but on return outbuf is filled with zeroes (or is never assigned to because I originally initialize everything to zero).
My initial thought is the object is not being passed by reference, but I assumed this should work because I found this deserialization function on another SO question (which I do not have the link for anymore).
So then I try to use the ref keyword, but then I get an error cannot convert from ref ID_OUTPUT to ref object.
As you're dealing with multiple structure types and want an unique method that does the job, generics are a good option. This allows you to use a nice C# syntax without the need to cast your instances as object.
// For .Net 4.5 and previous versions
public static T RawDeserialize<T>(byte[] bytearray)
where T : struct
{
var type = typeof(T);
int len = Marshal.SizeOf(type);
IntPtr i = IntPtr.Zero;
try
{
i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray, 0, i, len);
return (T)Marshal.PtrToStructure(i, type);
}
finally
{
if (i != IntPtr.Zero)
{
Marshal.FreeHGlobal(i);
}
}
}
Usage:
ID_OUTPUT myStruct = RawDeserialize<ID_OUTPUT>(someByteArray);
ZZZZ myStruct2 = RawDeserialize<ZZZZ>(someByteArray);
For .Net 4.5.1+, you may want use the generic versions of SizeOf/PtrToStructure:
public static T RawDeserialize<T>(byte[] bytearray)
where T : struct
{
int len = Marshal.SizeOf<T>();
IntPtr i = IntPtr.Zero;
try
{
i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray, 0, i, len);
return Marshal.PtrToStructure<T>(i);
}
finally
{
if (i != IntPtr.Zero)
{
Marshal.FreeHGlobal(i);
}
}
}
You are redefining the reference of the local variable obj to something different than you passed to the method. The actual reference in the calling method doesn't change. When you change the method signature to ref the variables, it does work, but then you have a conversion problem.
Since your method is a void, the simplest thing to do is return the variable and type it appropriately (or cast it if you need to). (You can use generics on your return variable, whatever suits you best).
Required method signature:
ID_OUTPUT /* your struct return variable */ RawDeserialize(byte[] bytearray);
If the problem is solely the conversion problem, this fixes it:
ID_OUTPUT obj;
object outputObject = obj;
RawDeserialize(bytearray, ref outputObject);
obj = (ID_OUTPUT)outputObject;

CLR Hosting: Call a function with an arbitrary method signature?

I need to take a C++ program, load CLR and call a function in a C# library. The function I need to call takes in a COM interface as the parameter.
My problem is, the CLR hosting interface only seems to let you call a method with this signature:
int Foo(String arg)
Example, this C++ code loads CLR and runs the P.Test function in "test.exe":
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(NULL, L"wks", 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*)&pClrHost);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(L"C:\\Test.exe", L"P", L"Test", L"", &retVal);
What I need to do is call a function with this method signature (note I own the C# code, so I can change it):
void SomeFunction(IFoo interface)
Where IFoo is a com interface. I could even do what I need to if I could call a function like this:
IntPtr SomeFunction();
I could have SomeFunction construct a correct delegate then use Marshal.GetFunctionPointerForDelegate. However, I can't figure out how to make the hosting interfaces do anything other than call a function with an int func(string) signature.
Does anyone know how to call a C# function from C++ code with a different signature?
(Note I cannot use C++/CLI for this. I need to use the hosting APIs.)
Edit: I promised to update my answer to include the code for passing 64-bit values, so here's goes..
I left the original answer if someone's interested in a less complicated solution for a 32-bit system.
Note: Since you're using CorBindToRuntimeEx, which is obsolete in .net 4.0, I'll assume a .net 2.0 compliant solution using good old Win32 API.
So, in order to pass data between C# and C++ (in our case - the IntPtr of a delegate), we'll create a small Win32 DLL project, named SharedMem, with two straight-forward methods.
SharedMem.h
#pragma once
#ifdef SHAREDMEM_EXPORTS
#define SHAREDMEM_API __declspec(dllexport)
#else
#define SHAREDMEM_API __declspec(dllimport)
#endif
#define SHAREDMEM_CALLING_CONV __cdecl
extern "C" {
SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV SetSharedMem(ULONGLONG _64bitValue);
SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV GetSharedMem(ULONGLONG* p64bitValue);
}
Now for the implementation file:
SharedMem.cpp
#include "stdafx.h"
#include "SharedMem.h"
HANDLE hMappedFileObject = NULL; // handle to mapped file
LPVOID lpvSharedMem = NULL; // pointer to shared memory
const int SHARED_MEM_SIZE = sizeof(ULONGLONG);
BOOL CreateSharedMem()
{
// Create a named file mapping object
hMappedFileObject = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
SHARED_MEM_SIZE,
TEXT("shmemfile") // Name of shared mem file
);
if (hMappedFileObject == NULL)
{
return FALSE;
}
BOOL bFirstInit = (ERROR_ALREADY_EXISTS != GetLastError());
// Get a ptr to the shared memory
lpvSharedMem = MapViewOfFile( hMappedFileObject, FILE_MAP_WRITE, 0, 0, 0);
if (lpvSharedMem == NULL)
{
return FALSE;
}
if (bFirstInit) // First time the shared memory is accessed?
{
ZeroMemory(lpvSharedMem, SHARED_MEM_SIZE);
}
return TRUE;
}
BOOL SetSharedMem(ULONGLONG _64bitValue)
{
BOOL bOK = CreateSharedMem();
if ( bOK )
{
ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem;
*pSharedMem = _64bitValue;
}
return bOK;
}
BOOL GetSharedMem(ULONGLONG* p64bitValue)
{
if ( p64bitValue == NULL ) return FALSE;
BOOL bOK = CreateSharedMem();
if ( bOK )
{
ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem;
*p64bitValue = *pSharedMem;
}
return bOK;
}
Note that for simplicity I'm just sharing a 64-bit value, but this is a general way of sharing memory between C# and C++. Feel free to enlarge SHARED_MEM_SIZE and/or add functions in order to share other data types as you see fit.
This is how we'll consume the above methods: we'll use SetSharedMem() on the C# side in order to set the delegate's IntPtr as a 64-bit value (regardless if the code runs on a 32 or a 64 bit system).
C# Code
namespace CSharpCode
{
delegate void VoidDelegate();
static public class COMInterfaceClass
{
[DllImport( "SharedMem.dll" )]
static extern bool SetSharedMem( Int64 value );
static GCHandle gcDelegateHandle;
public static int EntryPoint(string ignored)
{
IntPtr pFunc = IntPtr.Zero;
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
bool bSetOK = SetSharedMem( pFunc.ToInt64() );
return bSetOK ? 1 : 0;
}
public static void SomeMethod()
{
MessageBox.Show( "Hello from C# SomeMethod!" );
gcDelegateHandle.Free();
}
}
}
Note the use of GCHandle in order to prevent the delegate from being garbage-collected.
For good measures, we'll use the return value as a success/failure flag.
On the C++ side we'll extract the 64-bit value using GetSharedMem(), convert it to a function pointer and invoke the C# delegate.
C++ Code
#include "SharedMem.h"
typedef void (*VOID_FUNC_PTR)();
void ExecCSharpCode()
{
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(
NULL,
L"wks",
0,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(PVOID*)&pClrHost
);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(
szPathToAssembly,
L"CSharpCode.COMInterfaceClass",
L"EntryPoint",
L"",
&retVal // 1 for success, 0 is a failure
);
if ( hrExecute == S_OK && retVal == 1 )
{
ULONGLONG nSharedMemValue = 0;
BOOL bGotValue = GetSharedMem(&nSharedMemValue);
if ( bGotValue )
{
VOID_FUNC_PTR CSharpFunc = (VOID_FUNC_PTR)nSharedMemValue;
CSharpFunc();
}
}
}
The Original Answer - Good for 32-bit Systems
Here's a solution that is based on using IntPtr.ToInt32() in order to convert the delegate func. ptr. to the int which is returned from the static C# EntryPoint method.
(*) Note the use of GCHandle in order to prevent the delegate from being garbage-collected.
C# Code
namespace CSharpCode
{
delegate void VoidDelegate();
public class COMInterfaceClass
{
static GCHandle gcDelegateHandle;
public static int EntryPoint(string ignored)
{
IntPtr pFunc = IntPtr.Zero;
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
return (int)pFunc.ToInt32();
}
public static void SomeMethod()
{
MessageBox.Show( "Hello from C# SomeMethod!" );
gcDelegateHandle.Free();
}
}
}
C++ Code
We'll need to convert the returned int value to a function pointer, so we'll start off by defining a void function ptr. type:
typedef void (*VOID_FUNC_PTR)();
And the rest of the code looks pretty much like your original code, with the addition of converting and executing the function ptr.
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(
NULL,
L"wks",
0,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(PVOID*)&pClrHost
);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(
szPathToAssembly,
L"CSharpCode.COMInterfaceClass",
L"EntryPoint",
L"",
&retVal
);
if ( hrExecute == S_OK )
{
VOID_FUNC_PTR func = (VOID_FUNC_PTR)retVal;
func();
}
A Little Bit of Extra
You can also make use of the string input in order to choose which method to execute:
public static int EntryPoint( string interfaceName )
{
IntPtr pFunc = IntPtr.Zero;
if ( interfaceName == "SomeMethod" )
{
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
}
return (int)pFunc.ToInt32();
}
You can get even more generic by using reflection in order to find the correct method according to the given string input.

Marshal struct with array member in C#

I'm using C# with P/Invoke to access to a DLL method. The definition of the method is the following:
[DllImport("userManager.dll")]
static extern int GetUsers(out IntPtr userList);
Original structs:
typedef struct user_list {
unsigned short NumUsers;
USER_LIST_ITEM List[VARLEN];
} USER_LIST
typedef struct user_list_item {
char name[260];
unsigned char address[256];
} USER_LIST_ITEM
And the struct layout I've done is the following:
[StructLayout(LayoutKind.Sequential)]
public class USER_LIST
{
public uint NumUsers;
[MarshalAs(UnmanagedType.ByValArray)]
public USER_LIST_ITEM [] List;
}
[StructLayout(LayoutKind.Sequential)]
public class USER_LIST_ITEM
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string address;
};
But I get an error when I try to unmarshall it:
USER_LIST userList = new USER_LIST();
// Prepare pointer
IntPtr uList = Marshal.AllocHGlobal(Marshal.SizeOf(userList));
Marshal.StructureToPtr(userList, uList, false);
result = GetUsers(out uList);
Marshal.PtrToStructure(uList, userList); <--
The runtime has encountered a fatal error. The address of the error was at 0x79f82af6, on thread 0x464. 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.
I get the NumUsers property right, but it seems the error occurs when unmarshalling the array. Any thoughts?
If you specify an array in a structure used as an out parameter, you need to tell the marshaler what length is the array going to be. With your code, the marshaler is probably allocating a zero-length array or just using null, which produces the crash. Unfortunately there seems to be no way to specify a variable-length out array as a member of a structure, because MarshalAs.SizeParamIndex only works for methods. You might get away with specifying a large, constant-size array using MarshalAs.SizeConst, but generally you'd have to parse the (presumably callee-allocated) return buffer like this:
var count = Marshal.ReadInt32 (uList) ;
var users = new List<USER_LIST_ITEM> () ;
var ptr = (long)uList + 4 ;
for (int i = 0 ; i < count ; ++i)
{
users.Add (Marshal.PtrToStructure (typeof (USER_LIST_ITEM),
new IntPtr (ptr))) ;
ptr += Marshal.SizeOf (typeof (USER_LIST_ITEM)) ;
}
You'll have to pay extra attention to alignment&padding and 32/64 bit issues.
That is because List has not been allocated yet.
You will need initialize all the fields.
Another problem I see is with the following:
IntPtr uList = Marshal.AllocHGlobal(Marshal.SizeOf(userList));
...
result = GetUsers(out uList);
Are you sure that out should not be ref? Else there is no point (not sure if ref is correct either).
Update: Looking at your code again, you should be doing this (and avoid that memory leak poking your eye).
IntPtr uList;
var result = GetUsers(out uList);
var userlist = (USER_LIST) Marshal.PtrToStructure(ulist, typeof(USER_LIST));
Marshal.FreeHGlobal(ulist); // pray here or shoot the author of the C function
Update again:
Your p/invoke signature is likely wrong or you are interpreting it wrong.
I can guess it probably something like:
int GetUsers(USER_LIST* ulist);
And that what you have is not the same thing.
If this is case, the solution is easy.
Change USER_LIST to a class (but keep sequential layout) and use
// pinvoke sig
int GetUsers(USER_LIST ulist);
var ulist = new USER_LIST();
// initialize fields
var r = GetUsers(ulist);
-- or --
Call it by ref.
// pinvoke sig
int GetUsers(ref USER_LIST ulist);
var ulist = new USER_LIST();
// initialize fields
var r = GetUsers(ref ulist);
This way, you dont have to mess with manual marshalling, and I cant see anymore potential for memory leaks.
Final update:
Given the signature you posted, it looks like GetUsers returns a pointer to a list of USER_LIST with the return value being the count. Nice memory leak there.
Anyways, I would probably experiment with an unsafe approach here, and just walk thru the result , and make sure everything gets freed. (I still think you should shoot the author).
I think your original code isn't probably so wrong.
You've probably just used the wrong overload of Marshal.PtrToStructure.
Have you tried this?
[DllImport("userManager.dll")]
static extern int GetUsers(out IntPtr userList);
[DllImport("userManager.dll")]
static extern void UMFree(IntPtr userList);
static void Main()
{
IntPtr userList; // no need to allocate memory in managed code;
GetUsers(out userList); // memory is allocated by native function
USER_LIST u = (USER_LIST)Marshal.PtrToStructure(userList, typeof(USER_LIST));
UMFree(userList);
}
Using unsafe code:
public unsafe struct USER_LIST
{
public uint numUsers;
public USER_LIST_ITEM* list;
}
public unsafe struct USER_LIST_ITEM
{
public fixed byte name[260];
public fixed byte address[256];
}
class Program
{
[DllImport("userManager.dll")]
static unsafe extern int GetUsers(USER_LIST** userList);
[DllImport("userManager.dll")]
static unsafe extern int UMFree(USER_LIST* userList);
private static unsafe void Main()
{
USER_LIST* list;
GetUsers(&list);
UMFree(list);
}
}

How can I pass a pointer to an integer in C#

I have a C API with the signature:
int GetBuffer(char* buffer, int* maxSize)
In C, I will call it this way:
char buffer[4096];
int maxSize = 4096;
GetBuffer(buffer, &maxSize);
maxSize is set to the buffer size, and set with the actual size filled.
I need to call it from C#. How do I do that under "safe mode"?
One option is simply to use C# pointer types - this requires unsafe block (or modifier on method/class), and compiling with /unsafe:
[DllImport(...)]
static extern int GetBuffer(byte* buffer, ref int maxSize);
Buffer can be allocated in several different ways. One would be to use a pinned heap array:
fixed (byte* buffer = new byte[4096])
{
int maxSize = buffer.Length;
GetBuffer(buffer, ref maxSize);
}
Another is to use stackalloc, though this is only feasible for small buffers:
byte* buffer = stackalloc byte[4096];
int maxSize = 4096;
GetBuffer(buffer, ref maxSize);
This particular approach is virtually identical to your C code in terms of performance and allocation patterns.
Another option altogether is to use marshaling for heap arrays, and avoid pointers entirely.
[DllImport(...)]
static extern int GetBuffer([Out] byte[] buffer, ref int maxSize);
byte[] buffer = new byte[4096];
int maxSize = buffer.Length;
GetBuffer(buffer, ref maxSize);
This should work without unsafe code.
extern int GetBuffer(IntPtr buffer, ref int bufSize);
// ...
byte[] buf = new byte[kBufSize];
GCHandle handle = GCHandle.Alloc(buf, GCHandleType.Pinned); // possibly expensive call
IntPtr p = handle.AddrOfPinnedObject();
int size = buf.Length;
int ret = GetBuffer(p, ref size);
handle.Free();
You need to use what is called P\Invoke, and generate a function declaration that to reference the C function in the Dll from C#.
However, you have to be very careful when passing buffers in/out of unmanaged code. The framework will take care of some things for you but you may need to ensure that memory that you pass into the unmanaged call doesn't get moved by the Garbage collector.
[DllImport("Kernel32.dll", SetLastError=true)]
static extern Int32 GetBuffer(byte[] buffer,ref Int32 maxSize);
And to use it:
byte[] myBuf = new myBuf[4096];
Int32 maxSize = myBuf.Length;
GetBuffer(myBuf, ref maxSize);
Having a handle to the pointer doesn't fit the "safe mode" model at all; if the resource isn't managed by the Framework, it is unsafe.
One easy and safe option is to create a simple class that wraps the value, or any value like the following code:
public class Value<T> where T: struct
{
public static implicit operator T(Value<T> val)
{
return val.Value;
}
private T _value;
public Value(T value)
{
_value = value;
}
public Value() : this(default)
{
}
public T Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
public override string ToString()
{
return _value.ToString();
}
}
Passing on instances of this class instead of the value itself works almost like working with pointers.

Categories

Resources