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?
Related
let say we have following code in a dll written in C-lang, where i try to map some functions defined in the dll as functionpointers, map to their actual functions, i follow following link to get till here
https://learn.microsoft.com/en-us/dotnet/framework/interop/marshaling-different-types-of-arrays
Dlltest.h
typedef struct VersionInfo
{
UINT uMajor;
UINT uMinor;
UINT uMRevision;
} STRUCT_VERSION_INFO;
typedef struct DllTestFPStruct
{
int(*Close) (void);
int(*Init) (STRUCT_VERSION_INFO *sVersInfo);
int(*RegisterClient) (int id);
} STRUCT_DLL_TEST_FP_STRUCT;
typedef struct DllTestMgr
{
STRUCT_DLL_TEST_FP_STRUCT *psItf;
int iDummy;
} STRUCT_DLL_TEST_MGR;
extern "C"
{ __declspec(dllexport) void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP); }
Dlltest.c
static int Close(void);
static int Init(STRUCT_VERSION_INFO *sVersInfo);
static int RegisterClient(int id);
STRUCT_DLL_TEST_FP_STRUCT sFP =
{
&Close,
&Init,
&RegisterClient,
};
DLLTESTC_API void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP)
{ psFP->psItf = &sFP; }
static int Close(void)
{ printf("Closed called.\n"); }
static int Init(STRUCT_VERSION_INFO *sVersInfo)
{ printf("Init called.\n");}
static int RegisterClient(STRUCT_VERSION_INFO *sVersInfo)
{ printf("RegisterClient called.\n");}
Now i want to write a c# application which uses this DLL, specially it should make use of the "GetDllTestFP"-Function which maps the functionpointers to their actuall function. right now my C#-Application is as follow:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int FP_DLL_TEST_CLOSE(ref VersionInfo sVersInfo);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int FP_DLL_TEST_INIT(ref VersionInfo sVersInfo);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int FP_DLL_TEST_RC(ref VersionInfo sVersInfo);
[StructLayout(LayoutKind.Sequential)]
public struct DllTestFPStruct
{
public FP_DLL_TEST_CLOSE Close;
public FP_DLL_TEST_INIT Init;
public FP_DLL_TEST_RC RegisterClient;
}
[StructLayout(LayoutKind.Sequential)]
public struct DllTestMgr
{
public IntPtr psItf;
public int iDummy;
}
[DllImport("DllTestC.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void GetDllTestFP(ref DllTestMgr ps);
static void Main(string[] args)
{
VersionInfo vInfo = new VersionInfo();
DllTestFPStruct dllFPs = new DllTestFPStruct();
DllTestMgr dllTest = new DllTestMgr();
IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(dllFPs));
Marshal.StructureToPtr(dllFPs, buffer, false);
dllTest.psItf = buffer;
GetDllTestFP(ref dllTest); // Funtionpointers are not mapped, still null
dllFPs.Close(ref vInfo);
}
The problem is, that the functions do not get mapped to their actuall functions in the dll.
Any idea how can i achieve my goal?
Thanks
The C# code is:
DllTestMgr dllTest = new DllTestMgr();
GetDllTestFP(ref dllTest);
DllTestFPStruct dllFPs = (DllTestFPStruct)Marshal.PtrToStructure(dllTest.psItf, typeof(DllTestFPStruct));
VersionInfo vInfo = new VersionInfo();
dllFPs.Close(ref vInfo);
You don't need to allocate dllTest.psItf becase in GetDllTestFP you do:
DLLTESTC_API void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP)
{
psFP->psItf = &sFP;
}
So you copy the address of sFP.
Note that in general this is a bad idea, because you are giving to the "client" direct access to your data (the sFP struct). The alternative is that the client passes the memory (as you wrote before) and then you do:
(*psFP->psItf) = sFP;
(but then remember to free the allocated memory!)
Third alternative, the C-side allocates a block of memory through a shared allocator (one that can be used by C#, so no malloc/new here) and then the C# has to deallocate it.
wrong solution is
STRUCT_DLL_TEST_FP_STRUCT sFP2 = sFP;
psFP->psItf = &sFP2;
The lifetime of sFP2 ends when the method returns. psFP->psItf is now pointing to a piece of stack that doesn't exist anymore. don't do it!
Ah... as written by #Hans Passant, depending on who allocates the memory, the GetDllTestFP can be ref or out. If the memory is allocated by C# then it must be ref, if it isn't allocated (as is now) or is allocated by C++, then out is ok and you'll save on the marshaling in one direction.
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);
}
}
I have seen some examples that use reflection to identify fields of structs, and set their value, etc.
Is there a way to identify a field of a struct as an IntPtr within a helper function which frees the marshal'd memory which is also CLS-compliant?
Ex:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SMyStruct
{
public IntPtr pMyPtr;
}
public void myFunc()
{
SMyStruct Foo = new SMyStruct();
Foo.pMyPtr = Marshal.AllochGlobal(some amount of memory);
// Do something with Foo
DeAllocateStructMemory<SMyStruct>(ref Foo); // <-- My helper function that accepts a struct and attempts to free any IntPtrs within the structure
}
// Helper function pseudo code
public void DeAllocateStructMemory<T>(ref T sSomeStruct)
{
foreach (var field in typeof(T).GetFields(BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public))
{
if (field.FieldType == typeof(IntPtr))
{
var pMyData = field.GetValue(sSomeStruct);
IntPtr myPtr = (IntPtr) pMyData;
FreeMemory(ref myPtr);
// Set the IntPtr member to zero
// NOTE: NOT CLS Compliant
field.SetValueDirect(__makeref(sSomeStruct), IntPtr.Zero);
}
}
}
public void FreeMemory(ref IntPtr pMem)
{
if (pMem != IntPtr.Zero)
{
Marshal.FreeHGlobal(pMem);
pMem = IntPtr.Zero;
}
}
UPDATE #1 Believe I've found how to deallocate the memory, but still cannot set the struct member/field to IntPtr.Zero after the memory is freed.
UPDATE #2 Figured out how to set the IntPtr member of the struct, although it is through a method that is not CLS-Compliant. I am only using this application in Windows so I don't think its an issue, but open to suggestions if anyone knows of a better method.
I'm using Visual Studio 2010 and coding in c#.
public partial class App : Application
{
[DllImport("ewfapi.dll")]
public static extern IntPtr EwfMgrOpenProtected(string lpVolume);
[DllImport("ewfapi.dll")]
public static extern bool EwfMgrCommit(IntPtr hDevice);
public static bool EWFcommit()
{
temp = true;
string strVolumeName = "C:";
hProVol = EwfMgrOpenProtected(strVolumeName);
temp = EwfMgrCommit(hProVol);
return temp;
}
}
The problem I'm having is that these commands do not work on the machine with the EWF enabled.
I've attempted to get the Volume Name from ewfmanager instead of hardcoding it to "C:". However, I'm still learning and I'm having trouble using the command "EwfMgrGetProtectedVolumeList". This api command will return the VolumeName I need to run the other ewfapi commands. However, this command returns "PEWF_VOLUME_NAME_ENTRY " variable which I need to define. This is where I get stuck.
In C++, the header file defines this variable, but in c# header files are non existent. Would I have to convert C++ code to c# code in order to use structures defined in the header file?
Currently, I'm using a work around by executing the commands via command prompt which works flawlessly. But, I'm curious and want to learn the "right"/best way to do this in c#.
Please let me know of any experience using api commands in C#. Thank you.
This is the code I'm trying to convert to C#. I'm unsure how to convert the Declarators in C++ to C#.
typedef struct _EWF_VOLUME_NAME_ENTRY
{
struct _EWF_VOLUME_NAME_ENTRY* Next;
WCHAR Name[1];
} EWF_VOLUME_NAME_ENTRY, * PEWF_VOLUME_NAME_ENTRY;
This is the converted C# code without the declarators:
public struct EWF_VOLUME_NAME_ENTRY
{
/// _EWF_VOLUME_NAME_ENTRY*
public System.IntPtr Next;
/// WCHAR[1]
public string Name;
}
Your attempt was actually really close, it just needed some attributes to help the compiler along (in fact it may have worked without them)
[StructLayout(LayoutKind.Sequential)]
public struct EWF_VOLUME_NAME_ENTRY
{
/// _EWF_VOLUME_NAME_ENTRY*
public System.IntPtr Next;
/// WCHAR[1]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=1)]
public string Name;
}
To use it, the P/Invoke signature of your method would be.
[DllImport("ewfapi.dll")]
public static extern IntPtr EwfMgrGetProtectedVolumeList();
You also need a few more functions
[DllImport("ewfapi.dll")]
public static extern void EwfMgrVolumeNameListDelete(IntPtr list);
[DllImport("ewfapi.dll")]
public static extern bool EwfMgrVolumeNameListIsEmpty(IntPtr list);
[DllImport("ewfapi.dll")]
public static extern void EwfMgrVolumeNameEntryPop(ref IntPtr list);
You could then get the list of protected volumes like so.
public IEnumerable<string> GetProtectedVolumeNames()
{
var listptr = EwfMgrGetProtectedVolumeList();
if(listptr == IntPtr.Zero)
throw new Win32Exception(); //the default constuctor calls Marshal.GetLastWin32Error() for you.
try
{
while(!EwfMgrVolumeNameListIsEmpty(listPtr))
{
var currentStruct = Marshal.PtrToStructure<EWF_VOLUME_NAME_ENTRY>(listPtr);
// Pre .NET 4.5.1 version
// var currentStruct = (EWF_VOLUME_NAME_ENTRY)Marshal.PtrToStructure(listPtr, typeof(EWF_VOLUME_NAME_ENTRY));
yield return currentStruct.Name;
EwfMgrVolumeNameEntryPop(ref listPtr);
}
}
finally
{
if(listptr != IntPtr.Zero)
EwfMgrVolumeNameListDelete(listptr);
}
}
Note this code was written in the browser and is untested, but I think it should work.
EDIT: Important note, if you use this function outside of a foreach and instead manually go through the IEnumerable be sure to dispose of it, otherwise the finally block will not execute and you will have a memory leak (a foreach automatically calls dispose for you when you leave the scope of the loop).
On a side note you may want to check out this old MSDN Magazine article: "Making PInvoke Easy". It includes a link to a program that you can give it the C/C++ signature and it will give you back a rough version of the .NET P/Invoke signature.
Minor tweak to Scotts answer. The name is a null terminated string, so increase the SizeConst to get the rest of the characters of the name:
[StructLayout(LayoutKind.Sequential)]
public struct EWF_VOLUME_NAME_ENTRY
{
/// _EWF_VOLUME_NAME_ENTRY*
public System.IntPtr Next;
/// WCHAR[1]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=300)]
public string Name;
}
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;
}