I am trying to create C# code which calls a function in a DLL with a C interface. I am a C and C++ expert but my knowledge of C# is pretty limited. I have already managed to load the DLL into the C# program and to call functions whose interfaces use simple data types like int
or even strings. But this one exceeds my abilities, and I hope for some helpful hints.
The DLL declares this entry point:
BOOL WINAPI GetUsers(ALLUSERINFO* pInfo, DWORD size, DWORD* count);
where pInfo
points at a C array of structs with size
elements which the GetUsers
function fills with data. count
receives the number of array elements which were actually filled. Both pInfo
and count
are out-only.
ALLUSERINFO
is defined as
struct ALLUSERINFO : public USER_INFO2, public PRINCIPAL_INFO
{
WCHAR Name[10];
WCHAR Role[256];
};
and its bases classes are
struct USER_INFO2
{
WCHAR Name[80];
WCHAR Password[40];
};
struct PRINCIPAL_INFO
{
int PrincipalStatus;
WCHAR UniqueID[39];
};
(WCHAR
is a typedef for wchar_t
.)
I assume I must turn the base classes into embedded members of ALLUSERINFO
, but I am completely at loss regarding how to create an array of structs which contain fixed-size C strings in C# and then pass this array by pointer to its first element.
I found the StructLayout
and MarshalAs
attributes but from the docs which I read I am not even sure whether they are suitable in my case, much less how they work when it comes to an array of structs of structs, as in this case.
Thank you!
Finally I found a solution. It may not be elegant but it works.
I needed the following C# declarations:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal unsafe struct USER_INFO2
{
public fixed char Name[80];
public fixed char Password[40];
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal unsafe struct PRINCIPAL_INFO
{
internal int PrincipalStatus;
public fixed char UniqueID[39];
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal unsafe struct ALLUSERINFO
{
internal USER_INFO2 UserInfo;
internal PRINCIPAL_INFO PrincipalInfo;
public fixed char Name[10];
public fixed char Role[256];
}
[DllImport("mydll.dll", CharSet = CharSet.Unicode)]
internal static extern bool GetUsers(ALLUSERINFO* pInfo, uint size, out uint count);
Then the call to GetUsers
looks like this:
unsafe
{
ALLUSERINFO[] buffer = new ALLUSERINFO[maxUsers];
fixed (ALLUSERINFO* pBuffer = &buffer[0])
{
uint actualUsers;
bool res = DSQL.DSEnumAllUsers(pBuffer, maxUsers, out actualUsers);
if (!res)
return false;
// do something with the returned data in the buffer
}
}
I am not sure whether all the attributes are required in the struct
declarations. The crucial thing seems to be using the unsafe
and fixed
keywords.
Thanks to @Charlieface who helped me direct my thoughts into the correct direction!