Marshalling char** in a more human way - c#

I have the following interface method declaration in C#:
[ComVisible(true), ComImport, SuppressUnmanagedCodeSecurity,
Guid("A668B8F2-BA87-4F63-9D41-768F7DE9C50E"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ILAVAudioStatus
{
[PreserveSig]
int GetOutputDetails(out IntPtr pOutputFormat, out int pnChannels, out int pSampleRate, out uint pChannelMask);
}
pOutputformat is actually a char** in C++
I need to do a Marshal.PtrToStringAnsi(pOutputformat) to get the actual string.
Is there some marshalling attribute that can convert pOutputformat directly to an out string instead of using an IntPtr?
This is what the C++ method does:
HRESULT CLAVAudio::GetOutputDetails(const char **pOutputFormat, int *pnChannels, int *pSampleRate, DWORD *pChannelMask)
{
if(!m_pOutput || m_pOutput->IsConnected() == FALSE) {
return E_UNEXPECTED;
}
if (m_avBSContext) {
if (pOutputFormat) {
*pOutputFormat = get_sample_format_desc(SampleFormat_Bitstream);
}
return S_FALSE;
}
if (pOutputFormat) {
*pOutputFormat = get_sample_format_desc(m_OutputQueue.sfFormat);
}
if (pnChannels) {
*pnChannels = m_OutputQueue.wChannels;
}
if (pSampleRate) {
*pSampleRate = m_OutputQueue.dwSamplesPerSec;
}
if (pChannelMask) {
*pChannelMask = m_OutputQueue.dwChannelMask;
}
return S_OK;
}

Marshal.PtrToStringAnsi(pOutputformat)
is the best that you can do. There's no avoiding that. If you try to marshal as out string OutputFormat then the marshaller will call CoTaskMemFree on the pointer returned by the native code. And that ends in tears.
The question remains as to who is responsible for deallocating the memory that pOutputformat points to? Only you can know the answer to that.
One wonders why the designer of this COM interface chose to use C strings rather than the COM BSTR.

Related

COM interop and marshaling of VARIANT(VT_PTR)

We use a 3rd party COM object, one of which methods under certain conditions returns a VARIANT of VT_PTR type. That upsets the default .NET marshaler, which throws the following error:
Managed Debugging Assistant 'InvalidVariant' : 'An invalid VARIANT was
detected during a conversion from an unmanaged VARIANT to a managed
object. Passing invalid VARIANTs to the CLR can cause unexpected
exceptions, corruption or data loss.
Method signatures:
// (Unmanaged) IDL:
HRESULT getAttribute([in] BSTR strAttributeName, [retval, out] VARIANT* AttributeValue);
// C#:
[return: MarshalAs(UnmanagedType.Struct)]
object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);
Is there an elegant way to bypass such marshaler's behavior and obtain the underlying unmanaged pointer on the managed side?
What I've considered/tried so far:
A custom marshaler:
[return: MarshalAs(UnmanagedType.CustomMarshaler,
MarshalTypeRef = typeof(IntPtrMarshaler))]
object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);
I did implement IntPtrMarshaler, just to find the interop layer crashing the process even before any of my ICustomMarshaler methods gets called. Perhaps, the VARIANT* argument type is not compatible with custom marshalers.
Rewrite (or clone) the C# interface definition with getAttribute method redefined (like below) and do all the marshaling for output VARIANT manually:
void getAttribute(
[In, MarshalAs(UnmanagedType.BStr)],
string strAttributeName,
IntPtr result);
This doesn't seem nice (the interface itself has 30+ other methods). It'd also break existing, unrelated pieces of code which already make use of getAttribute without issues.
Obtain an unmanaged method address of getAttribute from vtable (using Marshal.GetComSlotForMethodInfo etc), then do the manual invocation and marshaling against my own custom delegate type (using Marshal.GetDelegateForFunctionPointer etc).
So far, I've taken this approach and it seem to work fine, but it feels as such an overkill for what should be a simple thing.
Am I missing some other feasible interop options for this scenario? Or, maybe there is a way to make CustomMarshaler work here?
What I would do is define a simple VARIANT structure like this:
[StructLayout(LayoutKind.Sequential)]
public struct VARIANT
{
public ushort vt;
public ushort r0;
public ushort r1;
public ushort r2;
public IntPtr ptr0;
public IntPtr ptr1;
}
And the interface like this;
[Guid("39c16a44-d28a-4153-a2f9-08d70daa0e22"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface MyInterface
{
VARIANT getAttributeAsVARIANT([MarshalAs(UnmanagedType.BStr)] string strAttributeName);
}
Then, add an extension method somewhere in a static class like this, so the caller can have the same coding experience using MyInterface:
public static object getAttribute(this MyInterface o, string strAttributeName)
{
return VariantSanitize(o.getAttributeAsVARIANT(strAttributeName));
}
private static object VariantSanitize(VARIANT variant)
{
const int VT_PTR = 26;
const int VT_I8 = 20;
if (variant.vt == VT_PTR)
{
variant.vt = VT_I8;
}
var ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf<VARIANT>());
try
{
Marshal.StructureToPtr(variant, ptr, false);
return Marshal.GetObjectForNativeVariant(ptr);
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
This will do nothing for normal variants, but will just patch it for VT_PTR cases.
Note this only works if the caller and the callee are in the same COM apartement.
If they are not, you will get the DISP_E_BADVARTYPE error back because marshaling must be done, and by default, it will be done by the COM universal marshaler (OLEAUT) which only support Automation compatible data types (just like .NET).
In this case, theoratically, you could replace this marshaler by another one (at COM level, not at NET level), but that would mean to add some code on C++ side and possibly in the registry (proxy/stub, IMarshal, etc.).
For my own future reference, here's how I ended up doing it, using the 3rd option mentioned in the question:
[ComImport, Guid("75A67021-058A-4E2A-8686-52181AAF600A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInterface
{
[return: MarshalAs(UnmanagedType.Struct)]
object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);
}
private delegate int IInterface_getAttribute(
IntPtr pInterface,
[MarshalAs(UnmanagedType.BStr)] string name,
IntPtr result);
public static object getAttribute(this IInterface obj, string name)
{
var ifaceType = typeof(IInterface);
var ifaceMethodInfo = ((Func<string, object>)obj.getAttribute).Method;
var slot = Marshal.GetComSlotForMethodInfo(ifaceMethodInfo);
var ifacePtr = Marshal.GetComInterfaceForObject(obj, ifaceType);
try
{
var vtablePtr = Marshal.ReadIntPtr(ifacePtr);
var methodPtr = Marshal.ReadIntPtr(vtablePtr, IntPtr.Size * slot);
var methodWrapper = Marshal.GetDelegateForFunctionPointer<IInterface_getAttribute>(methodPtr);
var resultVar = new VariantClass();
var resultHandle = GCHandle.Alloc(resultVar, GCHandleType.Pinned);
try
{
var pResultVar = resultHandle.AddrOfPinnedObject();
VariantInit(pResultVar);
var hr = methodWrapper(ifacePtr, name, pResultVar);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
if (resultVar.vt == VT_PTR)
{
return resultVar.ptr;
}
try
{
return Marshal.GetObjectForNativeVariant(pResultVar);
}
finally
{
VariantClear(pResultVar);
}
}
finally
{
resultHandle.Free();
}
}
finally
{
Marshal.Release(ifacePtr);
}
}

Using native vtable-based DLL interface from C#

Some time ago I wrote a Windows DLL in C++ to encapsulate some functionality that I wanted to share between different C++ and Delphi compilers. I always liked the way that COM allows me to work with objects in contrast to having a flat C API in a DLL. Since for this DLL I did not need the full power of COM and no reference counting, I decided to implement a DLL interface that somewhat mimics COM. I have a single exported C function that allows me to create an object and returns an vtable interface pointer. Via this pointer I can call methods on the object in a compiler neutral way in C++ and Delphi. When I'm done using the interface pointer, I call release on it.
Now I would like to use this DLL from C# but I don't know how to import the interface. Moreover I have a callback interface that the C# client must implement. Following is the C++ interface header file (somewhat shortended).
struct CksReadParams
{
...
};
struct ICksAdapter
{
virtual ~ICksAdapter();
virtual int32_t __cdecl Release() = 0;
virtual int32_t __cdecl ReadValues(CksReadParams* params) = 0;
};
struct ICksAdapterCallback
{
virtual ~ICksAdapterCallback();
virtual void __cdecl OnConnected(int32_t clientID) = 0;
virtual void __cdecl OnData(int32_t clientID, const char* csvLine) = 0;
virtual void __cdecl OnLog(int32_t clientID, const char* message) = 0;
virtual void __cdecl OnError(int32_t clientID, int32_t code, const char* message) = 0;
};
extern "C"
{
typedef int(__cdecl *CA_PCreateAdapter)(ICksAdapterCallback* callback, ICksAdapter** adapter);
__declspec(dllexport) int32_t __cdecl CA_CreateAdapter(ICksAdapterCallback* callback, ICksAdapter** adapter);
}
Can you give me a sketch on how to use this from C#, especially how to implement the callback interface, how to handle the __cdecl calling convention and how to handle char* parameters in the callback which are actually UTF8 encoded C++ strings?
I wrote the following code that seems to do what I need. It was mainly inspired by this blog post Implementing an unmanaged C++ interface callback. The code builds mainly on Marshal.GetDelegateForFunctionPointer and Marshal.GetFunctionPointerForDelegate which are available starting with .NET 4.5 which is acceptable in my case. To sum up, you need to know the vtable of the C++ object and you need to fake a vtable in unmanaged memory for the callback interface.
If it's worth the effort I will leave to the reader. In my case it at least allows me to reuse my DLL. Next time I will probably stick with a flat C API or use COM in the first place.
class CksAdapterCallback
{
private IntPtr instance;
public CksAdapterCallback()
{
instance = Marshal.AllocHGlobal(IntPtr.Size * 6);
IntPtr vtblPtr = IntPtr.Add(instance, IntPtr.Size);
Marshal.WriteIntPtr(instance, vtblPtr);
Marshal.WriteIntPtr(vtblPtr, IntPtr.Zero); //dummy entry for the destructor
OnConnectedInternal = new OnConnectedDelegate(OnConnected);
Marshal.WriteIntPtr(IntPtr.Add(vtblPtr, IntPtr.Size), Marshal.GetFunctionPointerForDelegate(OnConnectedInternal));
OnDataInternal = new OnDataDelegate(OnData);
Marshal.WriteIntPtr(IntPtr.Add(vtblPtr, 2*IntPtr.Size), Marshal.GetFunctionPointerForDelegate(OnDataInternal));
...
}
~CksAdapterCallback()
{
Marshal.FreeHGlobal(instance);
}
public IntPtr Instance
{
get { return instance; }
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void OnConnectedDelegate(IntPtr instance, Int32 clientID);
OnConnectedDelegate OnConnectedInternal;
void OnConnected(IntPtr instance, Int32 clientID)
{
...
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void OnDataDelegate(IntPtr instance, Int32 clientID, IntPtr data);
OnDataDelegate OnDataInternal;
void OnData(IntPtr instance, Int32 clientID, IntPtr data)
{
...
}
...
}
class CksAdapterProxy
{
private IntPtr instance;
public CksAdapterProxy(IntPtr instance)
{
this.instance = instance;
IntPtr vtblPtr = Marshal.ReadIntPtr(instance, 0);
IntPtr funcPtr = Marshal.ReadIntPtr(vtblPtr, 1 * IntPtr.Size);
ReleaseInternal = Marshal.GetDelegateForFunctionPointer<ReleaseDelegate>(funcPtr);
...
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate Int32 ReleaseDelegate(IntPtr instance);
ReleaseDelegate ReleaseInternal;
...
public Int32 Release()
{
return ReleaseInternal(instance);
}
...
}
public class CksAdapterTest
{
[DllImport("CKS\\CksAdapter.dll")]
[return: MarshalAs(UnmanagedType.I4)]
static extern int CA_CreateAdapter(IntPtr adapterCallback, out IntPtr adapter);
CksAdapterProxy adapter = null;
CksAdapterCallback adapterCallback = new CksAdapterCallback();
public CksAdapterTest()
{
IntPtr nativeAdapter = IntPtr.Zero;
int result = CA_CreateAdapter(adapterCallback.Instance, out nativeAdapter);
if(result == 0)
{
adapter = new CksAdapterProxy(nativeAdapter);
...
adapter.Release();
}
}
}

How to pass struct with string data in PINVOKE?

I'm stuck by passing struct with string data from C# code to C++ dll.
c++ code
typedef struct
{
LPCSTR lpLibFileName;
LPCSTR lpProcName;
LPVOID pPointer1;
LPVOID pPointer2;
} ENTITY, *PENTITY, *LPENTITY;
extern "C" __declspec(dllexport) int Test(LPENTITY entryList, int size);
int Test(LPENTITY entryList, int size)
{
for (int i = 0; i < size; i++)
{
ENTITY e = entryList[i];
// the char* value doesn't get passed correctly.
cout << e.lpLibFileName << e.lpProcName << endl;
}
return 0;
}
c# code
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private class Entity
{
public string lpLibFileName;
public string lpProcName;
public IntPtr pPointer1;
public IntPtr pPointer2;
}
[DllImport("cpp.dll")]
private static extern int Test(
[In, Out, MarshalAs(UnmanagedType.LPArray)]Entity[] entities,
int size);
static void Main(string[] args)
{
var entries = new[]
{
new Entity
{
lpLibFileName = "comdlg32",
lpProcName = "PrintDlgExW",
pPointer1 = Marshal.GetFunctionPointerForDelegate(new PrintDlgEx(PrintDlgExCallback)),
pPointer2 = IntPtr.Zero,
},
new Entity
{
lpLibFileName = "shell32",
lpProcName = "ShellAboutW",
pPointer1 = Marshal.GetFunctionPointerForDelegate(new ShellAbout(ShellAboutCallback)),
pPointer2 = IntPtr.Zero,
},
};
var ret = Test(entries, entries.Length);
}
The PINVOKE was triggered, but the char* data like lpLibFileName and lpProcName cannot be passed correctly. Did I miss something? How to correct it?
Thanks.
Your code maps a C# class onto a native struct. Because a C# class is a reference type then it will be marshalled as a reference. So your code passes an array of references that gets marshalled to an array of pointers on the native side.
But the native code expects a pointer to an array of structs which are value types. So the simplest solution is to change the declaration of Entity to be a struct rather than a class.
The other issues that I can see:
The native code appears to be using the cdecl calling convention. You'll need to change the C# code to match.
You are decorating the array parameter with Out. You cannot marshal modifications to the string fields back to the managed code.
You will need to make sure that you keep alive the delegates that you pass to GetFunctionPointerForDelegate to stop them being collected.
When passing parameter like array of custom structures, use 'struct' instead of 'class' when defining your own data structure. After I changing it back to struct, everything worked fine.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct Entity
{
public string lpLibFileName;
public string lpProcName;
public IntPtr pPointer1;
public IntPtr pPointer2;
}

How to marshal an Exception to an IntPtr in C#

I want to keep a pointer to a managed Exception object in an unmanaged C assembly.
I've tried a bunch of ways. This is the only one I've found that passes my very preliminary tests.
Is there a better way?
What I'd really like to do is handle the alloc and free methods in the ExceptionWrapper constructor and destructor, but structs can't have constructors or destructors.
EDIT: Re: Why I would like this:
My C structure has a function pointer that is set with a managed delegate marshaled as an unmanaged function pointer. The managed delegate performs some complicated measurements using external equipment and an exceptions could occur during those measurements. I'd like to keep track of the last one that occurred and its stack trace. Right now, I'm only saving the exception message.
I should point out that the managed delegate has no idea it's interacting with a C DLL.
public class MyClass {
private IntPtr _LastErrorPtr;
private struct ExceptionWrapper
{
public Exception Exception { get; set; }
}
public Exception LastError
{
get
{
if (_LastErrorPtr == IntPtr.Zero) return null;
var wrapper = (ExceptionWrapper)Marshal.PtrToStructure(_LastErrorPtr, typeof(ExceptionWrapper));
return wrapper.Exception;
}
set
{
if (_LastErrorPtr == IntPtr.Zero)
{
_LastErrorPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ExceptionWrapper)));
if (_LastErrorPtr == IntPtr.Zero) throw new Exception();
}
var wrapper = new ExceptionWrapper();
wrapper.Exception = value;
Marshal.StructureToPtr(wrapper, _LastErrorPtr, true);
}
}
~MyClass()
{
if (_LastErrorPtr != IntPtr.Zero) Marshal.FreeHGlobal(_LastErrorPtr);
}
}
This doesn't work. You are hiding a reference to the Exception object in unmanaged memory. The garbage collector cannot see it there so it cannot update the reference. When the C spits the pointer back out later, the reference won't point the object anymore after the GC has compacted the heap.
You'll need to pin the pointer with GCHandle.Alloc() so the garbage collector cannot move the object. And can pass the pointer returned by AddrOfPinnedObject() to the C code.
That's fairly painful if the C code holds on that pointer for a long time. The next approach is to give the C code a handle. Create a Dictionary<int, Exception> to store the exception. Have a static int that you increment. That's the 'handle' value you can pass to the C code. It is not perfect, you'll run into trouble when the program has added more than 4 billion exceptions and the counter overflows. Hopefully you'll never actually have that many exceptions.
You want serialization.
As a side note, your statement of:
What I'd really like to do is handle the alloc and free methods in the ExceptionWrapper constructor and destructor, but structs can't have constructors or destructors.
is not true. structs in C# can have and do have constructor(s), just not allowing user to declare parameterless constructor explicitly. That is, for example, you can declare a constructor which accepts an Exception. For destructors, which is not widely used in managed code, you should implement IDisposable if your class will hold some unmanaged resources.
Exception is non-blittable, you may not marshalling it the way you described, but it can be serialized as byte arry thus makes interop possible. I've read your another question:
Implications of throwing exception in delegate of unmanaged callback
and take the some of the usage from your code. Lets's to have two projects, one for managed and the other for the unmanaged code. You can create them with all the default settings but note the bitness of the executable images should be set the same. There are just one file per project need to be modified:
Managed console application - Program.cs:
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.InteropServices;
using System.IO;
using System;
namespace ConsoleApp1 {
class Program {
[DllImport(#"C:\Projects\ConsoleApp1\Debug\MyDll.dll", EntryPoint = "?return_callback_val##YGHP6AHXZ#Z")]
static extern int return_callback_val(IntPtr callback);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int CallbackDelegate();
static int Callback() {
try {
throw new Exception("something went wrong");
}
catch(Exception e) {
UnmanagedHelper.SetLastException(e);
}
return 0;
}
static void Main() {
CallbackDelegate #delegate = new CallbackDelegate(Callback);
IntPtr callback = Marshal.GetFunctionPointerForDelegate(#delegate);
int returnedVal = return_callback_val(callback);
var e = UnmanagedHelper.GetLastException();
Console.WriteLine("exception: {0}", e);
}
}
}
namespace ConsoleApp1 {
public static class ExceptionSerializer {
public static byte[] Serialize(Exception x) {
using(var ms = new MemoryStream { }) {
m_formatter.Serialize(ms, x);
return ms.ToArray();
}
}
public static Exception Deserialize(byte[] bytes) {
using(var ms = new MemoryStream(bytes)) {
return (Exception)m_formatter.Deserialize(ms);
}
}
static readonly BinaryFormatter m_formatter = new BinaryFormatter { };
}
}
namespace ConsoleApp1 {
public static class UnmanagedHelper {
[DllImport(#"C:\Projects\ConsoleApp1\Debug\MyDll.dll", EntryPoint = "?StoreException##YGHHQAE#Z")]
static extern int StoreException(int length, byte[] bytes);
[DllImport(#"C:\Projects\ConsoleApp1\Debug\MyDll.dll", EntryPoint = "?RetrieveException##YGHHQAE#Z")]
static extern int RetrieveException(int length, byte[] bytes);
public static void SetLastException(Exception x) {
var bytes = ExceptionSerializer.Serialize(x);
var ret = StoreException(bytes.Length, bytes);
if(0!=ret) {
Console.WriteLine("bytes too long; max available size is {0}", ret);
}
}
public static Exception GetLastException() {
var bytes = new byte[1024];
var ret = RetrieveException(bytes.Length, bytes);
if(0==ret) {
return ExceptionSerializer.Deserialize(bytes);
}
else if(~0!=ret) {
Console.WriteLine("buffer too small; total {0} bytes are needed", ret);
}
return null;
}
}
}
Unnamaged class library - MyDll.cpp:
// MyDll.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#define DLLEXPORT __declspec(dllexport)
#define MAX_BUFFER_LENGTH 4096
BYTE buffer[MAX_BUFFER_LENGTH];
int buffer_length;
DLLEXPORT
int WINAPI return_callback_val(int(*callback)(void)) {
return callback();
}
DLLEXPORT
int WINAPI StoreException(int length, BYTE bytes[]) {
if (length<MAX_BUFFER_LENGTH) {
buffer_length=length;
memcpy(buffer, bytes, buffer_length);
return 0;
}
return MAX_BUFFER_LENGTH;
}
DLLEXPORT
int WINAPI RetrieveException(int length, BYTE bytes[]) {
if (buffer_length<1) {
return ~0;
}
if (buffer_length<length) {
memcpy(bytes, buffer, buffer_length);
return 0;
}
return buffer_length;
}
With these code you can serialize the exception first and then deserialize it at any later time for retrieving the object that represents the original exception - just the reference would not be as same as the original, so are the objects it referenced.
I'd add some note of the code:
The dll name and method entry point of DllImport should be modified as your real build, I didn't manipulate the mangled names.
Unmanaged Helper is just a demonstration of the interop with the example unmanaged methods StoreException and RetrieveException; you don't have to write the code like how I deal with them, you might want to design in your own way.
If you want to deserialize the exception within the unmanaged code, you might want to design your own formatter rather than BinaryFormatter. I don't know an existing implementation of Microsoft's specification in non-cli C++.
In case you want to build the code in C instead of C++, you'll have to change the Compile As option(Visual Studio) and rename MyDll.cpp to MyDll.c; also note the mangled names would be like _StoreException#8; use DLL Export Viewer or hex editors to find the exact name.

Passing a Structure to C++ API using Marshal.StructureToPtr in C#

I am using API written in C++ in my code (writting in C#).
API requires a parameter as Pointer to Structure.
The Structure consists of "Int"s and Char Arrays:
for example
unsafe public struct ToBePassed
{
Int32 Num1;
Int32 Num2;
Char[] Data; // or fixed Char Data[255];
}
I can not directly pass the structure pointer to API because in that case, I am getting error as "Pointers cannot reference Marshaled structures". Code get compiled successfully but this Error comes when I execute (Debug) the code.
Now I have two options:
1st:- Passing Structure by Ref: I want to ask does an API requiring A Structure Pointer can receive the address when I pass the structure by ref. Note that API will return Data in "Char[] Data".
2nd:- Using Marshal.StructureToPtr: This will convert Structure Pointer to IntPtr. Again the Doubt is same, Will that API receive it correctly?
Thanks for Your Time!
Regards,
Swanand
If it only requires pointer, you can allocate some unmanaged memory, marshal the structure to the memory, and pass that pointer to your function. Then afterwards you could marshal back to the structure (if you wish) and free the memory. Before you marshal anything, you need to properly define the structure. Something like this:
[StructLayout(
LayoutKind.Sequential, //must specify a layout
CharSet = CharSet.Ansi)] //if you intend to use char
public struct ToBePassed
{
public Int32 Num1;
public Int32 Num2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
public Char[] Data; //specify the size using MarshalAs
}
[DllImport("...")]
public static extern void APICall(IntPtr argPtr);
public static void CallFunction(ToBePassed managedObj)
{
IntPtr unmanagedAddr = Marshal.AllocHGlobal(Marshal.SizeOf(managedObj));
Marshal.StructureToPtr(managedObj, unmanagedAddr, true);
APICall(unmanagedAddr);
Marshal.PtrToStructure(unmanagedAddr, managedObj);
Marshal.FreeHGlobal(unmanagedAddr);
unmanagedAddr = IntPtr.Zero;
}
[edit]
To simulate variable length arrays, allocate unmanaged memory within the structure and initialize as usual.
[StructLayout(LayoutKind.Sequential)]
public struct SomeStruct
{
public Int32 X;
public Int32 Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct VLA
{
public Int32 intArrayLength;
public Int32 SomeStructArrayLength;
public IntPtr intArray;
public IntPtr SomeStructArray;
}
public static VLA CreateVLA(int[] intArray, SomeStruct[] SomeStructArray)
{
var vla = new VLA()
{
intArrayLength = intArray.Length,
SomeStructArrayLength = SomeStructArray.Length,
intArray = Marshal.AllocHGlobal(intArray.Length * Marshal.SizeOf(typeof(int))),
SomeStructArray = Marshal.AllocHGlobal(SomeStructArray.Length * Marshal.SizeOf(typeof(SomeStruct))),
};
Marshal.Copy(intArray, 0, vla.intArray, intArray.Length);
//there's no overload to copy arbitrary arrays, do it manually
for (int i = 0; i < SomeStructArray.Length; i++)
{
Marshal.StructureToPtr(
SomeStructArray[i],
vla.SomeStructArray + i * Marshal.SizeOf(typeof(SomeStruct)),
true);
}
return vla;
}

Categories

Resources