How to Handle Null or Optional Dll Struct Parameters

How do I handle null or optional DLL struct parameters

You have a few options

1) Use a class instead of a struct

I think this method is the easiest. Simply declare the struct as a class:

[StructLayout(LayoutKind.Sequential)]
public class CStruct
{
//member-list
}

and then declare your method:

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static extern int DLLFunction(CStruct cStruct, ...);

If your optional parameter happens to be the last one, you can instead use CStruct cStruct = null as the parameter. This allows you to exclude it instead of passing null explicitly. You can also write a wrapper method that uses this and ensures the optional parameters come last.

2) Use IntPtr and IntPtr.Zero

Use a struct:

[StructLayout(LayoutKind.Sequential)]
public struct CStruct
{
//member-list
}

and declare your method as:

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static extern int DLLFunction(IntPtr cStruct, ...);

In the non-null case, marshal the struct to a pointer and call the method:

IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CStruct)));
try{
Marshal.StructureToPtr(myCStruct, ptr, false);
DLLFunction(ptr, ...);
} finally {
Marshal.FreeHGlobal(ptr);
}

In the null case, call the method with IntPtr.Zero:

DLLFunction(IntPtr.Zero, ...);

Again, you can make this parameter optional if this happens to be the last in the list (or you use a wrapper to make it so). Do this by using IntPtr cStruct = default(IntPtr) as the parameter. (As default(IntPtr) creates a IntPtr.Zero.)

3) Overload your method to avoid marshaling

Use a struct as in 2).

Simply declare one option for the non-null case:

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static extern int DLLFunction(ref cStruct, ...);

and another for the null case:

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static extern int DLLFunction(IntPtr cStruct, ...);

The first method will automatically get called when passing a struct, and the second when passing IntPtr.Zero. If declaring the IntPtr version with an optional parameter (as shown at the bottom of 2) above), it will automatically call it when you exclude the cStruct parameter.

4) Raw pointers using unsafe

Use a struct as in 2) and declare your method (note the unsafe keyword):

[DllImport("mydll.dll", OptionName = optionValue, ...)]
static unsafe extern int DLLFunction(CStruct* cStruct, ...);

In the non-null case, you pass &myCStruct, and simply null in the null case. As in 1), if this optional parameter is last, you can declare the parameter as CStruct* cStruct = null to automatically pass null when cStruct is excluded.

Thanks to @dialer for suggesting this method.

How to pass an Optional parameter that is a struct in C#

For this, you should declare the 3rd argument as a IntPtr.
When you want to pass it null, give it IntPtr.Zero.
If you want to pass a real structure to it, Marshal the structure into memory - i.e. something like this

SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
// set anything you want in the sa structure here

IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)));
try {
Marshal.StructureToPtr(sa, pnt, false)

// call RegSaveKey here
} finally {
Marshal.FreeHGlobal(pnt);
}

How to handle optional struct parameters when calling C functions from C#

You can change the struct to a class and then pass null. Remember that a C# struct is quite different from a C++ struct, and here your really want to use a C# class.

Or if you always want to ignore pioRecvRequest change the signature of SCardTransmit so that pioRecvRequest is of type IntPtr. Then pass IntPtr.Zero for the value.

Actually, the SCARD_IO_REQUEST is just a header and if you want to pass it in the call you will have to manage this structure and the additional buffer space yourself anyway so IntPtr is the right choice. You will then have to use the Marshal functions to allocate and fill the structure before the call and unmarshal the data and free it after the call.

How to pass a nullable type to a P/invoked function

It's not possible to pass a Nullable type into a PInvoke'd function without some ... interesting byte manipulation in native code that is almost certainly not what you want.

If you need the ability to pass a struct value as NULL to native code declare an overload of your PInvoke declaration which takes an IntPtr in the place of the struct and pass IntPtr.Zero

[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, ref int enumerator, IntPtr hwndParent, uint Flags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr enumerator, IntPtr hwndParent, uint Flags);

Note: I added a ref class to the first signature. If the native signature can take NULL, it is likely a pointer type. Hence you must pass value types by reference.

Now you can make calls like the following

if (enumerator.HasValue) { 
SetupDiGetClassDevs(someGuid, ref enumerator.Value, hwnd, flags);
} else {
SetupDiGetClassDevs(someGuid, IntPtr.Zero, hwnd, flags);
}

C#: How to pass null to a function expecting a ref?

I'm assuming that Mapping is a structure? If so you can have two versions of the FILES_GetMemoryMapping() prototype with different signatures. For the second overload where you want to pass null, make the parameter an IntPtr and use IntPtr.Zero

public static extern uint FILES_GetMemoryMapping(
[MarshalAs(UnmanagedType.LPStr)] string pPathFile,
out ushort Size,
[MarshalAs(UnmanagedType.LPStr)] string MapName,
out ushort PacketSize,
IntPtr oMapping,
out byte PagesPerSector);

Call example:

FILES_GetMemoryMapping(MapFile, out size, MapName,
out PacketSize, IntPtr.Zero, out PagePerSector);

If Mapping is actually a class instead of a structure, just set the value to null before passing it down.

How do I assign a null value to a struct for a pinvoke call?

Declare LSA_UNICODE_STRING to be a class rather than a struct. By doing so you make it a reference type. That matches the declaration of LSA_OBJECT_ATTRIBUTES because ObjectName has type PLSA_UNICODE_STRING which is a pointer to the struct. You do need to specify LayoutKind.Sequential when you do this since that's not the default for a class. Once you've made this change, you can set the variable to null.

[StructLayout(LayoutKind.Sequential)]
class PLSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}

You can adopt the same policy for LSA_OBJECT_ATTRIBUTES to allow that to be passed as null.

[StructLayout(LayoutKind.Sequential)]
class PLSA_OBJECT_ATTRIBUTES
{
public uint Length;
public IntPtr RootDirectory;
public PLSA_UNICODE_STRING ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}

[DllImport("advapi32.dll")]
static extern uint LsaOpenPolicy(
PLSA_UNICODE_STRING SystemName,
PLSA_OBJECT_ATTRIBUTES ObjectAttributes,
uint DesiredAccess,
out IntPtr PolicyHandle
);

Note that the declaration on pinvoke.net erroneously uses SetLastError=true on LsaOpenPolicy. That's wrong because the error code comes back in the return value. I also removed the setting of PreserveSig to true since that is the default. Their declaration of LSA_OBJECT_ATTRIBUTES also appears wrong since the ObjectName parameter has type LSA_UNICODE_STRING which is the struct rather than a pointer to it.

I would advise you to treat what you find on pinvoke.net with extreme scepticism. A large proportion of the declarations on that site are simply incorrect.

You ask about the possibility of using nullable types. According to @JaredPar's answer here, that's not an option.

SQL Query Advice - Most recent item

Assuming that "latest" is determined by date (rather than by issue number), this method is usually pretty fast, assuming decent indexes:

SELECT
T1.prodid,
T1.issue
FROM
Sales T1
LEFT OUTER JOIN dbo.Sales T2 ON
T2.custid = T1.custid AND
T2.prodid = T1.prodid AND
T2.datesold > T1.datesold
WHERE
T1.custid = @custid AND
T2.custid IS NULL

Handling 500k rows is something that a laptop can probably handle without trouble, let alone a real server, so I'd stay clear of denormalizing your database for "performance". Don't add extra maintenance, inaccuracy, and most of all headaches by tracking a "last sold" somewhere else.

EDIT: I forgot to mention... this doesn't specifically handle cases where two issues have the same exact datesold. You might need to tweak it based on your business rules for that situation.

How to P/Invoke SetFileTime with null parameters?

You cannot model that with the ref keyword in C#, that always produces a non-null pointer at runtime. You'll actually have to declare it long*, which requires using the unsafe keyword. Pass the argument with the &local-var syntax or use null.

One possible trick to avoid using unsafe is to declare 3 versions of this method with different names, using the DllImport's EntryPoint property to map them all to SetFileTime(). Specifying the ones you don't want to set as IntPtr so you can pass IntPtr.Zero, the one you do want to set as ref long. For example:

[DllImport("kernel32.dll", SetLastError = true, EntryPoint="SetFileTime", ExactSpelling=true)]
internal static extern bool SetFileCreateTime(
IntPtr hFile,
ref long lpCreationTime,
IntPtr lpLastAccessTimeUnused,
IntPtr lpLastWriteTimeUnused);

Repeat for SetFileAccessTime and SetFileWriteTime.

Calling Win API in C# with P/Invoke when _Out_ parameters can be NULL or non-NULL

When you have an optional parameter that is an enum, as you have here, then I think there is little alternative but to declare two separate overloads. The overload that you use to pass null declares the parameter like this:

IntPtr pCurrentTopologyId

You always pass IntPtr.Zero when calling this overload.

The other overload, the one that you call when you pass a non-null value declares the parameter like this:

out DISPLAYCONFIG_TOPOLOGY_ID pCurrentTopologyId

where DISPLAYCONFIG_TOPOLOGY_ID is a C# enum with base type of int.

Your code gets this second variant wrong because you declare it as out IntPtr pCurrentTopologyId. Well IntPtr is the wrong size on 64 bit (8 bytes rather than 4).

If you wish to declare just a single p/invoke and not use overloads then you just transfer the work elsewhere. To do that you have to pick the first option:

IntPtr pCurrentTopologyId

Fine when you want to pass IntPtr.Zero. But when you need to pass the address of an enum variable you need to pin your variable using GCHandle and AddrOfPinnedObject. All perfectly possible but yet more boilerplate. So, take your pick!



Related Topics



Leave a reply



Submit