COM interop and marshaling of VARIANT(VT_PTR) - c#

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);
}
}

Related

Marshalling Function Pointers with .NET 7 LibraryImport

I'm trying to implement some P/Invoke code using the new LibraryImport attribute, as opposed to the old DllImport. Specifically, I am trying to marshal a WNDCLASSEXW struct for use in RegisterClassEx.
Here is a simplified, shortened version of my managed implementation of WNDCLASSEXW:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WindowClass
{
private uint StructSize;
public WindowClassStyle Style;
[MarshalAs(UnmanagedType.FunctionPtr)]
public Win32API.WindowProcedure? WindowProcedure;
private int ClassAdditionalBytes;
private int WindowAdditionalBytes;
public IntPtr Instance;
public IntPtr Icon;
public IntPtr Cursor;
public IntPtr BackgroundBrush;
[MarshalAs(UnmanagedType.LPWStr)]
public string? ClassMenuResourceName;
[MarshalAs(UnmanagedType.LPWStr)]
public string? ClassName;
public IntPtr SmallIcon;
}
And my definition of Win32API.WindowProcedure:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate nint WindowProcedure(IntPtr windowHandle, MessageID messageID, nuint wParam, nint lParam);
And finally my definition of RegisterClassEx:
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
public static partial ushort RegisterClassEx(in WindowClass classDefinition);
However, this results in the error:
Error SYSLIB1051: The type 'xxx.WindowClass' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of parameter 'classDefinition'.
Therefore I believe I require custom marshalling for the WindowClass struct.
However, with this system being relatively new, I'm having difficulty finding good guidance on how to do this correctly and optimally. Previously, DllImport would magically marshal most types with little guidance, but LibraryImport appears to require more information, and be a bit stricter.
I could circumvent the problem by changing the type to IntPtr and requiring conversion of the delegate to IntPtr elsewhere in the program, but I would much prefer to do it as close to the managed/unmanaged boundary as possible and keep the structs and exposed native functions usable with more descriptive types.
Some of the resources I have found while searching:
The old P/Invoke documentation regarding delegates/function pointers
The new information regarding CustomMarshaller
The design documentation for the new source generator-based system
Primary Question: How do I correctly implement custom marshalling for my WNDPROC and the LP(C)WSTRs?
Question 2:
I would prefer to use a readonly struct, and turn all of the members into { get; init; } properties instead of fields, due to the nicer semantics. However I've noticed that the MarshalAs attribute cannot be applied to properties. Is there a good way to both use readonly structs with properties, while also providing the necessary information to ensure everything gets marshalled in/out correctly? Specifically for more complex types such as string? <-> LPCWSTR, delegate? <-> void*, and other such types I may encounter.
Bonus Question:
It appears that LibraryImport de-emphasizes the importance of specifying the correct calling convention. It's no longer part of the main attribute like DllImport, instead using a secondary attribute that looks like this: [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall) })] which frankly looks terrible. Is specifying calling convention necessary or beneficial now?
I was able to get it working with custom marshaling. While Simon's advice of changing the struct to contain the native types makes sense in the general case, in my case it doesn't, as these will be exposed for others to use.
The answer might be different for faster, more frequently called methods, but in this case registering a class and creating a window is inherently quite an expensive operation, so the added overhead of copying data to/from a different struct isn't worth any concern.
The marshaler was implemented like this:
[CustomMarshaller(typeof(WindowClass), MarshalMode.UnmanagedToManagedIn, typeof(WindowClassMarshaler))]
[CustomMarshaller(typeof(WindowClass), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToUnmanagedIn))]
internal static unsafe class WindowClassMarshaler
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal unsafe struct WindowClassUnmanaged
{
public uint StructSize;
public uint Style;
public IntPtr WindowProcedure;
public int ClassAdditionalBytes;
public int WindowAdditionalBytes;
public IntPtr Instance;
public IntPtr Icon;
public IntPtr Cursor;
public IntPtr BackgroundBrush;
public char* ClassMenuResourceName;
public char* ClassName;
public IntPtr SmallIcon;
}
internal static unsafe WindowClass ConvertToManaged(WindowClassUnmanaged unmanaged)
{
return new()
{
WindowProcedure = Marshal.GetDelegateForFunctionPointer<Win32API.WindowProcedure>(unmanaged.WindowProcedure),
ClassMenuResourceName = MarshalHelpers.Win32WideCharArrToString(unmanaged.ClassMenuResourceName),
ClassName = MarshalHelpers.Win32WideCharArrToString(unmanaged.ClassName),
// (remainder omitted, just simple copies)
};
}
internal unsafe ref struct ManagedToUnmanagedIn
{
public static int BufferSize => sizeof(WindowClassUnmanaged);
private byte* UnmanagedBufferStruct;
private char* UnmanagedStrResourceName, UnmanagedStrClassName;
public void FromManaged(WindowClass managed, Span<byte> buffer)
{
IntPtr WindowProcedure = (managed.WindowProcedure == null) ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(managed.WindowProcedure);
this.UnmanagedStrResourceName = (managed.ClassMenuResourceName == null) ? null : (char*)Marshal.StringToHGlobalUni(managed.ClassMenuResourceName);
this.UnmanagedStrClassName = (managed.ClassName == null) ? null : (char*)Marshal.StringToHGlobalUni(managed.ClassName);
WindowClassUnmanaged Result = new()
{
WindowProcedure = WindowProcedure,
ClassMenuResourceName = this.UnmanagedStrResourceName,
ClassName = this.UnmanagedStrClassName,
// (remainder omitted, just simple copies)
};
Span<byte> ResultByteView = MemoryMarshal.Cast<WindowClassUnmanaged, byte>(MemoryMarshal.CreateSpan(ref Result, 1));
Debug.Assert(buffer.Length >= ResultByteView.Length, "Target buffer isn't large enough to hold the struct data.");
ResultByteView.CopyTo(buffer);
this.UnmanagedBufferStruct = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(buffer));
}
public byte* ToUnmanaged() => this.UnmanagedBufferStruct;
public void Free()
{
if (this.UnmanagedStrResourceName != null)
{
Marshal.FreeHGlobal((nint)this.UnmanagedStrResourceName);
this.UnmanagedStrResourceName = null;
}
if (this.UnmanagedStrClassName != null)
{
Marshal.FreeHGlobal((nint)this.UnmanagedStrClassName);
this.UnmanagedStrClassName = null;
}
}
}
}
With this helper function to convert a Win32 LP(C)WSTR into a regular .NET string:
public static unsafe string? Win32WideCharArrToString(char* unmanagedArr)
{
if (unmanagedArr == null) { return null; }
int Length = 0;
while (*(unmanagedArr + Length) != 0x0000) { Length++; }
return Encoding.Unicode.GetString((byte*)unmanagedArr, Length * sizeof(char));
}
The nicer WindowClass struct is pretty much the same as before, except readonly, and with all elements being { get; init; }. The MarshalAs attributes on members are no longer required, as the custom marshaling handles everything.
Finally, the actual extern function now looks like this:
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
[UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall) })]
public static partial ushort RegisterClassEx([MarshalUsing(typeof(WindowClassMarshaler))] WindowClass classDefinition);
Note that this has been corrected. Previously I used the in keyword on the parameter, but this causes it to pass a pointer to the pointer to the struct data, which is an extra level of indirection that will cause the code to fail. Above is the updated version that works correctly.
I've tested and verified this works both in regular publish modes, as well as with AOT compilation, which was the reason for using LibraryImport in this case.
My bonus question still stands however, is there any benefit in specifying stdcall using UnmanagedCallConv?

.NET 6 IDispatch client implementation crash

.NET 6 has trouble calling its own IDispatch objects, if marshaled.
To reproduce:
if (Array.IndexOf(Environment.GetCommandLineArgs(), "/s")>=0) //Server
{
Thread t = new Thread(new ThreadStart(() =>
{
Hello h = new Hello();
new RunningObjectTable().Register("HelloTest", h);
Thread.Sleep(1000 * 3600);
}));
t.SetApartmentState(ApartmentState.MTA);
t.Start();
Thread.Sleep(1000 * 3600);
}
else //Client
{
object o = new RunningObjectTable().Get("HelloTest");
IHello h = o as IHello;
int f = h.Foo();
Console.WriteLine(h);
}
[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")] //IID_IDispatch
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(TypeLibTypeFlags.FDispatchable)]
public interface IHello
{
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
[DispId(1)]
public int Foo();
}
public class Hello: IHello
{
public int Foo()
{
Debug.WriteLine("Hello from server");
return 19;
}
}
Run the program with /s, that's the object server. Then run another copy to become the client.
The method invocation line in the client crashes with "Member not found" - HRESULT 0x80020003, DISP_E_MEMBERNOTFOUND. Normally it would mean a bogus DISPID, but where would the discrepancy possibly come from?
One minor infraction in that code is that the interface is decorated with the IID of IDispatch. With a custom IID, as COM prescribes, it doesn't even unmarshal as the dispinterface (the as IHello line returns null); internally, there's a QueryInterface call across processes with said custom IID, the dispinterface is not registered under HKCR\Interfaces, so the interprocess COM machinery doesn't know how to marshal it. At least that's my theory.
A similar piece of logic works fine if the server is a native (C++) one. If the same C# piece is recompiled against .NET framework 4.72, it doesn't even get that far, the o as IHello; line returns null.
RunningObjectTable is a helper class around the ROT. For completeness' sake, here:
internal class RunningObjectTable
{
#region API
[DllImport("ole32.dll")]
private static extern int CreateItemMoniker([MarshalAs(UnmanagedType.LPWStr)] string
lpszDelim, [MarshalAs(UnmanagedType.LPWStr)] string lpszItem,
out IMoniker ppmk);
[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
#endregion
private IRunningObjectTable m_rot;
public RunningObjectTable()
{
GetRunningObjectTable(0, out m_rot);
}
private IMoniker CreateItemMoniker(string s)
{
IMoniker mon;
CreateItemMoniker("", s, out mon);
return mon;
}
public int Register(string ItemName, object o)
{
return m_rot.Register(0, o, CreateItemMoniker(ItemName));
}
public void Unregister(int ROTCookie)
{
m_rot.Revoke(ROTCookie);
}
public object Get(string ItemName)
{
object o;
m_rot.GetObject(CreateItemMoniker(ItemName), out o);
return o;
}
}
Can someone with access to .NET Core internals please tell me what's going on?
Here's what is going on. The .NET object supports two dispinterfaces - one corresponding to IHello, another corresponding to Hello itself, and the latter is treated as the default one. I've confirmed that by adding an extra method Bar() to Hello, and checking the type in a native C++ client:
IUnknownPtr pUnk;
ROTGet(L"HelloTest", &pUnk);
IDispatchPtr pDisp(pUnk);
LPOLESTR Name = (LPOLESTR)L"Bar";
DISPID dispid;
hr = pDisp->GetIDsOfNames(IID_NULL, &Name, 1, 0, &dispid);
And I got S_OK and a valid, if bogus, DISPID. I then requested a DISPID for Foo in the same way, and got another bogus DISPID.
I've found two ways to fix that.
One, tell the framework that it should not implicitly generate a dispinterface for Hello. Annotate the class with [ClassInterface(ClassInterfaceType.None)], and it won't. The explicit check for the DISPID of Foo confirms that. The unmarshaled dispinterface in the client corresponds to IHello, and the methods work as expected.
Another approach, give IHello an identity (an IID) and make it marshalable. If I slap a custom GUID on that interface, and add a key with that GUID to HKCR\Interfaces, providing ProxyStubClsid32 equals to {00020420-0000-0000-C000-000000000046} (that's PSDispatch, the proxy/stub CLSID for IDispatch), the fragment also works.
One other way to tell COM that an IID corresponds to a dispinterface and should be marshaled as one is via implementing IStdMarshalInfo. I didn't try this.
One more note: the Hello class has to be declared public for this to work. If it's not, for some reason the as IHello returns null.

How to use D3D12GetDebugInterface API from c#?

I am currently trying to use Direct3D from c# and I've started by using the D3D12GetDebugInterface API.
The C++ syntax of the function is as follows (copied from the Microsoft documentation):
HRESULT D3D12GetDebugInterface(
REFIID riid,
void **ppvDebug
);
I'm having trouble importing the function into C#. I thought that maybe riid should be a pointer to a Guid struct, and I'm just using an IntPtr for **ppvDebug. Since **ppvDebug is a pointer to a pointer to an ID3D12Debug interface, I tried reimplementing the ID3D12Debug interface in C# code and using Marshal.PtrToStructure() to resolve the IntPtr to a usable ID3D12Debug interface instance, but that won't work. I remember reading about the ID3D12Debug interface being a COM object, but don't you need an ID for a COM object so you can import it? I haven't found any sort of COM ID anywhere in the documentation.
Anyway here's my latest attempt at getting something out of the function:
[DllImport("D3D12.dll")]
static extern int D3D12GetDebugInterface(IntPtr riid, IntPtr ppvDebug);
void func() {
unsafe
{
IntPtr DebugControllerPtr = IntPtr.Zero;
Type InterfaceType = typeof(ID3D12Debug);
Guid ID = InterfaceType.GUID;
IntPtr ptr = Marshal.AllocHGlobal(sizeof(Guid));
Marshal.StructureToPtr(ID, ptr, false);
D3D12GetDebugInterface(ptr, DebugControllerPtr);
Marshal.FreeHGlobal(ptr);
ID3D12Debug DebugController = null;
Marshal.PtrToStructure(DebugControllerPtr, DebugController);
DebugController.EnableDebugLayer();
}
}
In case you want to see my ID3D12Debug interface:
interface ID3D12Debug
{
void EnableDebugLayer();
}
As I said, I think Direct3D makes use of COM, which I am completely missing here, so maybe that's why it doesn't work.
There are usually many ways to declare interop code. Here is one that should work:
public static void Main()
{
D3D12GetDebugInterface(typeof(ID3D12Debug).GUID, out var obj);
var debug = (ID3D12Debug)obj;
debug.EnableDebugLayer(); // for example
}
[DllImport("D3D12")]
public static extern int D3D12GetDebugInterface([MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvDebug);
[Guid("344488b7-6846-474b-b989-f027448245e0"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ID3D12Debug
{
[PreserveSig]
void EnableDebugLayer();
}
You must add interface Guid and InterfaceType attributes for COM interfaces.
Guid can be passed easily by reference using UnmanagedType.LPStruct
You don't need unsafe code here.
You should check for errors (undone in my code)
If you need .NET interface definitions with DirectX, you can use this open source project here: https://github.com/smourier/DirectN, for example: https://github.com/smourier/DirectN/blob/master/DirectN/DirectN/Generated/ID3D12Debug.cs

Marshalling char** in a more human way

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.

ref keyword and wrapper methods

I have an interface, IProxy, and an implemenation Proxy. The purpose of the proxy is to wrap some extern functions which call into a C library. For the extern declaritions this requires that the structs be passed using the ref keyword. Here's a sample:
namespace CFuncs {
[DllImport("cLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int MyCFunc(ref MyStruct result);
}
public interface IProxy { public int MyFunc(MyStruct result); }
public class Proxy : IProxy {
public int MyFunc(MyStruct result) {
return CFuncs.MyCFunc(ref result);
}
}
The function declartion from the C header file:
int MyCFunc (MY_STRUCT* result);
typedef struct {
// some fields
} MY_STRUCT;
And the calling code:
var proxy = new Proxy();
var result = new MyStruct();
proxy.MyFunc(result);
My question is should I be using ref in the wrapper interface and implemenation as well, or will the struct be properly filled with data by the C function?
EDIT: The code presented as a sample DOES seem to work. Could that be that even though the C library wants a pointer it never actually does change it? Is ref a requirement for interop-ing with functions that take pointers? The sample C code that comes with the library always has the caller allocating memory for the structs it takes, if that means anything.
.NET uses call by value, and moreover struct is a value type -- hence, if you want it changed by an external function, it should be a ref parameter.

Categories

Resources