SetupDiEnumDeviceInterfaces on 64bit architecture on C# - c#

I try to call Window API function SetupDiEnumDeviceInterfaces from C# on 64bits architecture.
I import function and declare additional structures.
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern bool SetupDiEnumDeviceInterfaces(
IntPtr deviceInfoSet,
SP_DEVINFO_DATA deviceInfoData,
ref Guid interfaceClassGuid,
int memberIndex,
SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
[StructLayout(LayoutKind.Sequential)]
internal class SP_DEVINFO_DATA
{
internal int cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
internal Guid classGuid = Guid.Empty; // temp
internal int devInst = 0; // dumy
internal int reserved = 0;
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
internal int cbSize;
internal short devicePath;
}
Then I call this function as follows:
int index = 0;
Guid _classGuid = Guid.Empty;
IntPtr _deviceInfoSet = IntPtr.Zero;
Native.SP_DEVICE_INTERFACE_DATA interfaceData = new Native.SP_DEVICE_INTERFACE_DATA();
if (!Native.SetupDiEnumDeviceInterfaces(_deviceInfoSet, null, ref _classGuid, index, interfaceData))
{
int error = Marshal.GetLastWin32Error();
if (error != Native.ERROR_NO_MORE_ITEMS)
throw new Win32Exception(error);
break;
}
If runnig on 32bits architecture then all is well.
If runnig on 64bits architecture then SetupDiEnumDeviceInterfaces return false with last win error equal 1784.
The reason is that in struct interfaceData field cbSize has not valid value for 64bits architecture(as int alias Int32).
From official documentation
DeviceInterfaceData [out] A pointer to a caller-allocated buffer that
contains, on successful return, a completed SP_DEVICE_INTERFACE_DATA
structure that identifies an interface that meets the search
parameters. The caller must set DeviceInterfaceData.cbSize to
sizeof(SP_DEVICE_INTERFACE_DATA) before calling this function.
Trying to replace the type int(alias Int32) of the type Int64 for fields: cbSize, devInt, reserved.
How Can I replace class Guid for 64bits architecture?
If I try replace Guid simply of the type long:
[StructLayout(LayoutKind.Sequential)]
internal class SP_DEVICE_INTERFACE_DATA
{
internal Int64 cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
internal long interfaceClassGuid = 0; // temp
internal Int64 flags = 1;
internal Int64 reserved = 0;
}
With such a structure definition all works but I lose the convenience of working with a special class for guid. In the class definition Guid also used the type int so the right size will not be calculated on 64bits architecture.

You need to use uint in all fields(not int), and IntPtr in Reserved field.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SP_DEVINFO_DATA
{
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SP_DEVICE_INTERFACE_DATA
{
public uint cbSize;
public Guid InterfaceClassGuid;
public uint Flags;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
public uint cbSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string DevicePath;
}
To set cbSize in code use this:
Win32.SP_DEVICE_INTERFACE_DATA did = new Win32.SP_DEVICE_INTERFACE_DATA();
did.cbSize = (uint)Marshal.SizeOf(did);
Win32.SP_DEVICE_INTERFACE_DETAIL_DATA didd = new Win32.SP_DEVICE_INTERFACE_DETAIL_DATA();
didd.cbSize = (uint)Marshal.SizeOf(didd);

You might try setting your
[StructLayout(LayoutKind.Sequential, Pack = 2)]
internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
to:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
From what I've read, Pack = 8 for 32 bit, Pack = 1 for 64 bit.

The problem is not your GUID declarations; the reason SetupDiEnumDeviceInterfaces is failing out on 64-bit platforms is that you're not using the correct data type for the reserved field on each of SP_DEVINFO_DATA and SP_DEVICE_INTERFACE_DATA.
The structure definitions for SP_DEVINFO_DATA and SP_DEVICE_INTERFACE_DATA show that the reserved fields are declared as UINT_PTR, which is a pointer type. These should be declared in your P/Invoke types as IntPtr.
(Also, all of your int fields should instead be defined as uint, as those fields map to the DWORD native type.)
[StructLayout(LayoutKind.Sequential)]
internal class SP_DEVINFO_DATA
{
internal uint cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
internal Guid classGuid;
internal uint devInst;
internal IntPtr reserved;
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
internal uint cbSize;
internal short devicePath;
}
[StructLayout(LayoutKind.Sequential)]
internal class SP_DEVICE_INTERFACE_DATA
{
internal uint cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
internal Guid interfaceClassGuid;
internal uint flags;
internal IntPtr reserved;
}

Related

How to marshal WCHAR* in C#

I'm trying to pinvoke a function that receives a couple of WCHAR <paramName>[1] parameters.
From what I've read in multiple places, in C/C++ you can't actually pass arrays to functions, instead they get converted to a pointer holding the first element of the array, which means that the array length becomes irrelevant and therefore WCHAR <paramName>[1] is the same as WCHAR* <paramName>.
So normally I'd declare this as a StringBuilder in C# and marshal it as LPWStr, but in this particular case that throws all sorts of errors.
Basically, the function above receives a GET_VIRTUAL_DISK_INFO, which in turn holds a union of some structs and other loose fields. And it is a couple of the structs in the union that receive a WCHAR <paramName>[1].
When I try to marshal it as LPWStr and declare my field as StringBuilder or string I get the error [...] contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.
I've also tried declaring it as an IntPtr and then using Marshal.PtrToStringAuto(<IntPtr>) but I get an empty string.
My C# code with all the structs, enums etc:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Permissions;
namespace ConsoleApplication1
{
class Program
{
public static Guid VirtualStorageTypeVendorMicrosoft = new Guid("EC984AEC-A0F9-47e9-901F-71415A66345B");
static void Main(string[] args)
{
var handle = new VirtualDiskSafeHandle();
var storageType = new VirtualStorageType
{
DeviceId = VirtualStorageDeviceType.Vhdx,
VendorId = VirtualStorageTypeVendorMicrosoft
};
var parameters = new OpenVirtualDiskParameters
{
Version = OpenVirtualDiskVersion.Version2
};
var result = OpenVirtualDisk(ref storageType, "D:\\Test2.vhdx", VirtualDiskAccessMask.None, OpenVirtualDiskFlag.None,
ref parameters, ref handle);
if (result != 0)
{
throw new Win32Exception((int) result);
}
var info = new GetVirtualDiskInfo {Version = GetVirtualDiskInfoVersion.PhysicalDisk};
var infoSize = (uint) Marshal.SizeOf(info);
uint sizeUsed = 0;
result = GetVirtualDiskInformation(handle, ref infoSize, ref info, ref sizeUsed);
if (result != 0)
{
throw new Win32Exception((int) result);
}
Console.WriteLine($"LogicalSizeSector = {info.Union.PhysicalDisk.LogicalSectorSize}");
Console.WriteLine($"PhysicalSizeSector = {info.Union.PhysicalDisk.PhysicalSectorSize}");
Console.ReadLine();
}
[DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
public static extern uint OpenVirtualDisk(
[In] ref VirtualStorageType virtualStorageType,
[In] string path,
[In] VirtualDiskAccessMask virtualDiskAccessMask,
[In] OpenVirtualDiskFlag flags,
[In] ref OpenVirtualDiskParameters parameters,
[In, Out] ref VirtualDiskSafeHandle handle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(
[In] IntPtr hObject);
[DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
public static extern uint GetVirtualDiskInformation(
[In] VirtualDiskSafeHandle virtualDiskHandle,
[In, Out] ref uint virtualDiskInfoSize,
[In, Out] ref GetVirtualDiskInfo virtualDiskInfo,
[In, Out] ref uint sizeUsed);
[SecurityPermission(SecurityAction.Demand)]
public class VirtualDiskSafeHandle : SafeHandle
{
public VirtualDiskSafeHandle() : base(IntPtr.Zero, true) { }
public override bool IsInvalid => IsClosed || (handle == IntPtr.Zero);
public bool IsOpen => !IsInvalid;
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
public override string ToString()
{
return handle.ToString();
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParameters
{
public OpenVirtualDiskVersion Version; //OPEN_VIRTUAL_DISK_VERSION
public OpenVirtualDiskParametersUnion Union;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParametersUnion
{
[FieldOffset(0)]
public OpenVirtualDiskParametersVersion1 Version1;
[FieldOffset(0)]
public OpenVirtualDiskParametersVersion2 Version2;
[FieldOffset(0)]
public OpenVirtualDiskParametersVersion3 Version3;
}
/// <summary>
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParametersVersion1
{
public uint RWDepth; //ULONG
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParametersVersion2
{
public bool GetInfoOnly; //BOOL
public bool ReadOnly; //BOOL
public Guid ResiliencyGuid; //GUID
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParametersVersion3
{
public bool GetInfoOnly; //BOOL
public bool ReadOnly; //BOOL
public Guid ResiliencyGuid; //GUID
public Guid SnapshotId; //GUID
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfo
{
public GetVirtualDiskInfoVersion Version; //GET_VIRTUAL_DISK_INFO_VERSION
public GetVirtualDiskInfoUnion Union;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoUnion
{
[FieldOffset(0)] public GetVirtualDiskInfoSize Size;
[FieldOffset(0)] public Guid Identifier; //GUID
[FieldOffset(0)] public GetVirtualDiskInfoParentLocation ParentLocation;
[FieldOffset(0)] public Guid ParentIdentifier; //GUID
[FieldOffset(0)] public uint ParentTimestamp; //ULONG
[FieldOffset(0)] public VirtualStorageType VirtualStorageType; //VIRTUAL_STORAGE_TYPE
[FieldOffset(0)] public uint ProviderSubtype; //ULONG
[FieldOffset(0)] public bool Is4kAligned; //BOOL
[FieldOffset(0)] public bool IsLoaded; //BOOL
[FieldOffset(0)] public GetVirtualDiskInfoPhysicalDisk PhysicalDisk;
[FieldOffset(0)] public uint VhdPhysicalSectorSize; //ULONG
[FieldOffset(0)] public ulong SmallestSafeVirtualSize; //ULONGLONG
[FieldOffset(0)] public uint FragmentationPercentage; //ULONG
[FieldOffset(0)] public Guid VirtualDiskId; //GUID
[FieldOffset(0)] public GetVirtualDiskInfoChangeTrackingState ChangeTrackingState;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoSize
{
public ulong VirtualSize; //ULONGLONG
public ulong PhysicalSize; //ULONGLONG
public uint BlockSize; //ULONG
public uint SectorSize; //ULONG
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoParentLocation
{
public bool ParentResolved; //BOOL
public IntPtr ParentLocationBuffer; //WCHAR[1]
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoPhysicalDisk
{
public uint LogicalSectorSize; //ULONG
public uint PhysicalSectorSize; //ULONG
public bool IsRemote; //BOOL
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoChangeTrackingState
{
public bool Enabled; //BOOL
public bool NewerChanges; //BOOL
public IntPtr MostRecentId; //WCHAR[1]
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct VirtualStorageType
{
public VirtualStorageDeviceType DeviceId; //ULONG
public Guid VendorId; //GUID
}
public enum GetVirtualDiskInfoVersion
{
Unspecified = 0,
Size = 1,
Identifier = 2,
ParentLocation = 3,
ParentIdentifier = 4,
ParentTimestamp = 5,
VirtualStorageType = 6,
ProviderSubtype = 7,
Is4KAligned = 8,
PhysicalDisk = 9,
VhdPhysicalSectorSize = 10,
SmallestSafeVirtualSize = 11,
Fragmentation = 12,
IsLoaded = 13,
VirtualDiskId = 14,
ChangeTrackingState = 15
}
public enum VirtualStorageDeviceType
{
Unknown = 0,
Iso = 1,
Vhd = 2,
Vhdx = 3,
Vhdset = 4
}
public enum VirtualDiskAccessMask
{
None = 0x00000000,
AttachRo = 0x00010000,
AttachRw = 0x00020000,
Detach = 0x00040000,
GetInfo = 0x00080000,
Create = 0x00100000,
Metaops = 0x00200000,
Read = 0x000d0000,
All = 0x003f0000,
Writable = 0x00320000
}
[Flags]
public enum OpenVirtualDiskFlag
{
None = 0x00000000,
NoParents = 0x00000001,
BlankFile = 0x00000002,
BootDrive = 0x00000004,
CachedIo = 0x00000008,
CustomDiffChain = 0x00000010,
ParentCachedIo = 0x00000020,
VhdsetFileOnly = 0x00000040
}
public enum OpenVirtualDiskVersion
{
Unspecified = 0,
Version1 = 1,
Version2 = 2,
Version3 = 3,
}
}
}
Please comment out the line:
[FieldOffset(0)] public GetVirtualDiskInfoChangeTrackingState ChangeTrackingState;
Without that the struct results will be completely screwed up, still trying to figure out why.
Marshal the struct from the function call as an IntPtr. You will need to use Marshal.AllocHGlobal or another similar technique to get a block of unmanaged memory, since the Marshal isn't going to do it for you. You can then load the size member manually, or using Marshal.StructureToPtr.
From there use Marshal.OffsetOf to get the offset to the Union member. Once that's done use Marshal.Read and Marshal.PtrToStringUni to get at the data. For example with the Parent Location information:
IntPtr raw = Marshal.AllocHGlobal(1024);
// This is the GetVirtualDiskInfo from your provided code.
GetVirtualDiskInfo info = new GetVirtualDiskInfo();
info.Version = GetVirtualDiskInfoVersion.ParentLocation;
Marshal.StructureToPtr(info, raw, true);
Class1.Test(raw); // Replace this with your call to the function,
// This is a call to a C++/CLI method I wrote to stuff data
// into the structure.
IntPtr offsetToUnion = Marshal.OffsetOf(typeof(GetVirtualDiskInfo), "Union");
IntPtr data = raw + offsetToUnion.ToInt32();
bool parentResolved = Marshal.ReadInt32(data) != 0;
string parentLocationBuffer = Marshal.PtrToStringUni(data + 4);
Marshal.FreeHGlobal(raw); // Don't forget this!
Here's the method in the C++/CLI that loads the data for testing:
static void Test(IntPtr ptr)
{
GET_VIRTUAL_DISK_INFO* info = (GET_VIRTUAL_DISK_INFO*)ptr.ToPointer();
info->ParentLocation.ParentResolved = TRUE;
memcpy(info->ParentLocation.ParentLocationBuffer, L"123456789", 20);
}

EnumJobs returning a different JOB_INFO_1 size than Marshal.SizeOF

I'm calling the Win32 function EnumJobs (http://msdn.microsoft.com/en-us/library/windows/desktop/dd162625(v=vs.85).aspx) from managed code (C#).
[DllImport("Winspool.drv", SetLastError = true, EntryPoint = "EnumJobsA")]
public static extern bool EnumJobs(
IntPtr hPrinter, // handle to printer object
UInt32 FirstJob, // index of first job
UInt32 NoJobs, // number of jobs to enumerate
UInt32 Level, // information level
IntPtr pJob, // job information buffer
UInt32 cbBuf, // size of job information buffer
out UInt32 pcbNeeded, // bytes received or required
out UInt32 pcReturned // number of jobs received
);
EnumJobs(_printerHandle, 0, 99, 1, IntPtr.Zero, 0, out nBytesNeeded, out pcReturned);
I'm specifying Level 1 to receive a JOB_INFO_1 but the problem I'm having is the above function is returning nBytesNeeded as 240 per struct while the Marshal.SizeOf(typeof(JOB_INFO_1)) is 64 bytes causing a memory exception when I run Marshal.PtrToStructure. Counting the bytes manually for the struct gives 64 so I'm at a bit of a loss as to why I'm receiving the 240 byte structures from function, any insight would be appreciated.
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet=CharSet.Unicode)]
public struct JOB_INFO_1
{
public UInt32 JobId;
public string pPrinterName;
public string pMachineName;
public string pUserName;
public string pDocument;
public string pDatatype;
public string pStatus;
public UInt32 Status;
public UInt32 Priority;
public UInt32 Position;
public UInt32 TotalPages;
public UInt32 PagesPrinted;
public SYSTEMTIME Submitted;
}
The size of 64 is indeed correct for JOB_INFO_1 but if you look closely at the documentation, it talks about an array of structs :
pJob [out]
A pointer to a buffer that receives an array of JOB_INFO_1, JOB_INFO_2, or JOB_INFO_3 structures.
Additionally it is written :
The buffer must be large enough to receive the array of structures and any strings or other data to which the structure members point.
So there are bytes here for extra data beside the structs themselves.
Solution:
Populate the structs, increment pointer for next struct and ignore the remaining bytes.
Complete example:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
namespace WpfApplication3
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// Get handle to a printer
var hPrinter = new IntPtr();
bool open = NativeMethods.OpenPrinterW("Microsoft XPS Document Writer", ref hPrinter, IntPtr.Zero);
Debug.Assert(open);
/* Query for 99 jobs */
const uint firstJob = 0u;
const uint noJobs = 99u;
const uint level = 1u;
// Get byte size required for the function
uint needed;
uint returned;
bool b1 = NativeMethods.EnumJobsW(
hPrinter, firstJob, noJobs, level, IntPtr.Zero, 0, out needed, out returned);
Debug.Assert(!b1);
uint lastError = NativeMethods.GetLastError();
Debug.Assert(lastError == NativeConstants.ERROR_INSUFFICIENT_BUFFER);
// Populate the structs
IntPtr pJob = Marshal.AllocHGlobal((int) needed);
uint bytesCopied;
uint structsCopied;
bool b2 = NativeMethods.EnumJobsW(
hPrinter, firstJob, noJobs, level, pJob, needed, out bytesCopied, out structsCopied);
Debug.Assert(b2);
var jobInfos = new JOB_INFO_1W[structsCopied];
int sizeOf = Marshal.SizeOf(typeof (JOB_INFO_1W));
IntPtr pStruct = pJob;
for (int i = 0; i < structsCopied; i++)
{
var jobInfo_1W = (JOB_INFO_1W) Marshal.PtrToStructure(pStruct, typeof (JOB_INFO_1W));
jobInfos[i] = jobInfo_1W;
pStruct += sizeOf;
}
Marshal.FreeHGlobal(pJob);
// do something with your structs
}
}
public class NativeConstants
{
public const int ERROR_INSUFFICIENT_BUFFER = 122;
}
public partial class NativeMethods
{
[DllImport("kernel32.dll", EntryPoint = "GetLastError")]
public static extern uint GetLastError();
}
public partial class NativeMethods
{
[DllImport("Winspool.drv", EntryPoint = "OpenPrinterW")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenPrinterW([In] [MarshalAs(UnmanagedType.LPWStr)] string pPrinterName,
ref IntPtr phPrinter, [In] IntPtr pDefault);
[DllImport("Winspool.drv", EntryPoint = "EnumJobsW")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumJobsW([In] IntPtr hPrinter, uint FirstJob, uint NoJobs, uint Level, IntPtr pJob,
uint cbBuf, [Out] out uint pcbNeeded, [Out] out uint pcReturned);
}
[StructLayout(LayoutKind.Sequential)]
public struct JOB_INFO_1W
{
public uint JobId;
[MarshalAs(UnmanagedType.LPWStr)] public string pPrinterName;
[MarshalAs(UnmanagedType.LPWStr)] public string pMachineName;
[MarshalAs(UnmanagedType.LPWStr)] public string pUserName;
[MarshalAs(UnmanagedType.LPWStr)] public string pDocument;
[MarshalAs(UnmanagedType.LPWStr)] public string pDatatype;
[MarshalAs(UnmanagedType.LPWStr)] public string pStatus;
public uint Status;
public uint Priority;
public uint Position;
public uint TotalPages;
public uint PagesPrinted;
public SYSTEMTIME Submitted;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
}
Result:
Job 1:
Job 2 :
EDIT
It seems that you will have to get a little more robust checking than I did, because EnumJobs seems to return true when there are no jobs in queue. In the case of my example the assertion will fail but that won't mean that the code is wrong; just make sure that you have some jobs in the queue for the purpose of testing the function.

GUID is not passing to SP_DEVICE_INTERFACE_DATA structure

I am using the SetupDiEnumDeviceInterfaces function to get the device interfaces that are contained in a device information set. But the GUID is not passing "SP_DEVICE_INTERFACE_DATA" structure. Here is my code snippet.
I have tried to see what is the issue by using GetLastError.It always returns zero.
//GUID.
GetHidGuid(Myguid)
[DllImport("hid.dll", SetLastError = true)]
static extern unsafe void GetHidGuid(
ref GUID lpHidGuid);
[StructLayout(LayoutKind.Sequential)]
public unsafe struct GUID
{
public int Data1;
public System.UInt16 Data2;
public System.UInt16 Data3;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] data4;
}
// SetupDiEnumDeviceInterfaces function.
public unsafe int CT_SetupDiEnumDeviceInterfaces(int memberIndex)
{
int ErrorStatus;
mySP_DEVICE_INTERFACE_DATA = new SP_DEVICE_INTERFACE_DATA();--> here is where i Have problem.GUID is zero.
mySP_DEVICE_INTERFACE_DATA.cbSize = Marshal.SizeOf(mySP_DEVICE_INTERFACE_DATA);
int result = SetupDiEnumDeviceInterfaces(
hDevInfo,
0,
ref MYguid,
memberIndex,
ref mySP_DEVICE_INTERFACE_DATA);
return result;
ErrorStatus = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
}
public unsafe struct SP_DEVICE_INTERFACE_DATA
{
public int cbSize;
public GUID InterfaceClassGuid;
public int Flags;
public int Reserved;
}
any help is appreciated. Thanks in adv.
From pInvoke, it seems your GetHidGuid should be declared as
[DllImport("hid.dll", EntryPoint="HidD_GetHidGuid", SetLastError=true)]
static extern void HidD_GetHidGuid(out Guid hidGuid);
Another complete example is here

Problem reading port information

I have a problem retrieving the information of printer ports. I already use XcvData function to add, configure and delete ports, but I can not get the following to run as I expect it to:
public PrinterNativeMethods.PORT_DATA_1 GetPortData1FromPort(string serverName, string portName)
{
IntPtr printerHandle;
PrinterNativeMethods.PRINTER_DEFAULTS defaults = new PrinterNativeMethods.PRINTER_DEFAULTS
{
DesiredAccess = PrinterNativeMethods.PrinterAccess.ServerAdmin
};
string connection = string.Format(#"{0},XcvPort {1}", serverName, portName);
PrinterNativeMethods.OpenPrinter(connection, out printerHandle, ref defaults);
PrinterNativeMethods.CONFIG_INFO_DATA_1 configData = new PrinterNativeMethods.CONFIG_INFO_DATA_1
{
dwVersion = 1,
};
uint size = (uint)Marshal.SizeOf(configData);
IntPtr pointer = Marshal.AllocHGlobal((int)size);
Marshal.StructureToPtr(configData, pointer, true);
PrinterNativeMethods.PORT_DATA_1 portData = new PrinterNativeMethods.PORT_DATA_1();
uint portDataSize = (uint)Marshal.SizeOf(portData);
IntPtr portDataHandle = Marshal.AllocHGlobal((int)portDataSize);
try
{
uint outputNeeded;
uint status;
var retVal = PrinterNativeMethods.XcvData(printerHandle, "GetConfigInfo", pointer, size, out portDataHandle, portDataSize, out outputNeeded, out status);
//portDataHandle now points to a different location!? Unmarshalling will fail:
portData = (PrinterNativeMethods.PORT_DATA_1)Marshal.PtrToStructure(portDataHandle, typeof(PrinterNativeMethods.PORT_DATA_1));
}
finally
{
PrinterNativeMethods.ClosePrinter(printerHandle);
Marshal.FreeHGlobal(pointer);
Marshal.FreeHGlobal(portDataHandle);
}
return portData;
}
from PrinterNativeMethods:
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int XcvData(
IntPtr handle,
string dataName,
IntPtr inputData,
uint inputDataSize,
out IntPtr outputData,
uint outputDataSize,
out uint outputNeededSize,
out uint status);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct PORT_DATA_1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string sztPortName;
public uint dwVersion;
public uint dwProtocol;
public uint cbSize;
public uint dwReserved;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)]
public string sztHostAddress;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33h)]
public string sztSNMPCommunity;
public uint dwDoubleSpool;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
public string sztQueue;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
public string sztIPAddress;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 540)]
public byte[] Reserved;
public uint dwPortNumber;
public uint dwSNMPEnabled;
public uint dwSNMPDevIndex;
}
Additional comment: I cannot use WMI or prnadmin.dll as an alternative.
The problem you are having is in the definition of your XcvData definition. The outputData parameter by MS's definition is simply wanting a pointer to write data to, an IntPtr is a pointer, however by setting the parameter to out IntPtr you are making it a pointer to a pointer, this is why the address of your parameter appears to be changing. Changing the signature to the below will fix your problem.
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int XcvData(
IntPtr handle,
string dataName,
IntPtr inputData,
uint inputDataSize,
IntPtr outputData,
uint outputDataSize,
out uint outputNeededSize,
out uint status);
You can also avoid some unnecessary allocation/deallocation by changing things around a bit.
Change your method signature for XcvData to
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int XcvData(
IntPtr handle,
string dataName,
IntPtr inputData,
uint inputDataSize,
ref PORT_DATA_1 outputData,
uint outputDataSize,
out uint outputNeededSize,
out uint status);
Assuming that you will be using XcvData for more than just this single call you can make multiple references to it with slightly different signatures by setting the EntryPoint property on the DllImport Attribute.
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint="XcvData")]
internal static extern int XcvDataGetPortData1(
IntPtr handle,
string dataName,
IntPtr inputData,
uint inputDataSize,
ref PORT_DATA_1 outputData,
uint outputDataSize,
out uint outputNeededSize,
out uint status);
I have given this a quick test on my machine and can confirm that this will fix your problem.

C# P/Invoke structure problem

I am trying to write a C# P/Invoke wrapper for a C API (a native Win dll), and generally this is working fine. The only exception is a specific method which takes a struct as a parameter in the C code. The function is invoked without any exceptions, but it returns false indicating that something failed in the execution.
In the API header file the involved method and structs are defined as follows:
#define MAX_ICE_MS_TRACK_LENGTH 256
typedef struct tagTRACKDATA
{
UINT nLength;
BYTE TrackData[MAX_ICE_MS_TRACK_LENGTH];
} TRACKDATA, FAR* LPTRACKDATA;
typedef const LPTRACKDATA LPCTRACKDATA;
BOOL ICEAPI EncodeMagstripe(HDC /*hDC*/,
LPCTRACKDATA /*pTrack1*/,
LPCTRACKDATA /*pTrack2*/,
LPCTRACKDATA /*pTrack3*/,
LPCTRACKDATA /*reserved*/);
I have made an attempt to create a C# P/Invoke wrapper using the following code:
public const int MAX_ICE_MS_TRACK_LENGTH = 256;
[StructLayout(LayoutKind.Sequential)]
public class MSTrackData {
public UInt32 nLength;
public readonly Byte[] TrackData = new byte[MAX_ICE_MS_TRACK_LENGTH];
}
[DllImport("ICE_API.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool EncodeMagstripe(IntPtr hDC,
[In]ref MSTrackData pTrack1,
[In]ref MSTrackData pTrack2,
[In]ref MSTrackData pTrack3,
[In]ref MSTrackData reserved);
Then I try to invoke the EncodeMagstripe method using the following C# code:
CardApi.MSTrackData trackNull = null;
CardApi.MSTrackData track2 = new CardApi.TrackData();
byte[] trackBytes = Encoding.ASCII.GetBytes(";0123456789?");
track2.nLength = (uint)trackBytes.Length;
Buffer.BlockCopy(trackBytes, 0, track2.TrackData, 0, trackBytes.Length);
if (!CardApi.EncodeMagstripe(hDC, ref trackNull, ref track2, ref trackNull, ref trackNull)) {
throw new ApplicationException("EncodeMagstripe failed", Marshal.GetLastWin32Error());
}
This causes an ApplicationException to be thrown, and the error code is 801 which according to the documentation means "Data includes too many characters for the selected Track 2 format.". However the selected track format should allow up to 39 characters (I have also tried shorter strings).
I suspect the problem occurrs due to something I did wrong in the MSTrackData definition, but I cannot see what this may be. Does anyone have any suggestions?
All the answers given so far have a bit of the answer but are incomplete. You need the MarshalAs - ByValArray as well as the new, your MSTrackDatas are already references so you do not need to pass them by ref and you must check what calling convention ICEAPI represents, if it is StdCall you don't need to change anything but if it is cdecl you will need to add the CallingConvention to your DllImport attribute. Also, you may need to add a MarshalAs attribute to your bool return value to make sure it is marshaled as 4 byte WinApi style bool. Here are the declares you'll (probably) need:
public const int MAX_ICE_MS_TRACK_LENGTH = 256;
[StructLayout(LayoutKind.Sequential)]
public class MSTrackData {
public UInt32 nLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public Byte[] TrackData = new byte[MAX_ICE_MS_TRACK_LENGTH];
}
[DllImport("ICE_API.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EncodeMagstripe(IntPtr hDC,
[In] MSTrackData pTrack1,
[In] MSTrackData pTrack2,
[In] MSTrackData pTrack3,
[In] MSTrackData reserved);
I would define the BYTE array not with new, but use the following code instead to initialise the right size:
[MarshalAs(UnmanagedType.byValTSt, SizeConst =256)]
public readonly Byte[] TrackData;
I have used this successfully on char arrays in the past.
Looks to me like the problem is that you're passing a reference by reference. Since MSTrackData is a class (i.e. reference type), passing it by reference is like passing a pointer-to-pointer.
Change your managed prototype to:
public static extern bool EncodeMagstripe(IntPtr hDC,
MSTrackData pTrack1,
MSTrackData pTrack2,
MSTrackData pTrack3,
MSTrackData reserved);
See the MSDN article about passing structures.
I had almost exactly the same problem - but with ReadMagstripe. And the solution provided here for EncodeMagstripe did not work for ReadMagstripe! I think the reason it did not work was that ReadMagstripe has to return data into the TRACKDATA structure/class, while EncodeMagstripe only passes data to the dll and data in TRACKDATA does not need to be changed. Here is the implementation that eventually worked for me - both with EncodeMagstripe and ReadMagstripe:
public const int MAX_ICE_MS_TRACK_LENGTH = 256;
[StructLayout(LayoutKind.Sequential)]
public struct TRACKDATA
{
public UInt32 nLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szTrackData;
}
[DllImport("ICE_API.dll", EntryPoint="_ReadMagstripe#20", CharSet=CharSet.Auto,
CallingConvention=CallingConvention.Winapi, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReadMagstripe(int hdc, ref TRACKDATA ptrack1, ref TRACKDATA ptrack2,
ref TRACKDATA ptrack3, ref TRACKDATA reserved);
[DllImport("ICE_API.dll", EntryPoint="_EncodeMagstripe#20", CharSet=CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError=true)]
public static extern bool EncodeMagstripe(int hdc, [In] ref TRACKDATA ptrack1, [In] ref TRACKDATA ptrack2,
[In] ref TRACKDATA ptrack3, [In] ref TRACKDATA reserved);
/*
....
*/
private void EncodeMagstripe()
{
ICE_API.TRACKDATA track1Data = new ICE_API.TRACKDATA();
ICE_API.TRACKDATA track2Data = new ICE_API.TRACKDATA();
ICE_API.TRACKDATA track3Data = new ICE_API.TRACKDATA();
ICE_API.TRACKDATA reserved = new ICE_API.TRACKDATA();
//if read magstripe
bool bRes = ICE_API.ReadMagstripe(printer.Hdc, ref track1Data, ref track2Data,
ref track3Data, ref reserved);
//encode magstripe
if (bRes)
{
track2Data.szTrackData = "1234567890";
track2Data.nLength = 10;
bRes = ICE_API.EncodeMagstripe(printer.Hdc, ref track1Data, ref track2Data, ref track3Data, ref reserved);
}
}

Categories