I'm trying to create a Vulkan wrapper in C#, but I have some problems when I call a function. I rewrote the vulkan.h header as follows :
public static class Vk {
[StructLayout(LayoutKind.Sequential)] public class Instance { }
public enum Result {
...
}
public enum StructureType {
...
}
[StructLayout(LayoutKind.Sequential)] public class ApplicationInfo {
public StructureType sType;
public IntPtr pNext;
public string pApplicationName;
public uint applicationVersion;
public string pEngineName;
public uint engineVersion;
public uint apiVersion;
}
[StructLayout(LayoutKind.Sequential)] public class InstanceCreateInfo {
public StructureType sType;
public IntPtr pNext;
public uint flags_VkInstanceCreateFlags;
public ApplicationInfo pApplicationInfo;
public uint enabledLayerCount;
public string[] ppEnabledLayerNames;
public uint enabledExtensionCount;
public string[] ppEnabledExtensionNames;
}
[DllImport("vulkan-1.dll", EntryPoint = "vkCreateInstance")]
public extern static Result CreateInstance(
InstanceCreateInfo pCreateInfo,
IntPtr AllocationCallbacks_pAllocator,
out IntPtr pInstance_Instance);
}
The original declaration in C of this function is
VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance);
Now when I call my function, I'm doing like this :
Vk.InstanceCreateInfo instance_create_info = new Vk.InstanceCreateInfo();
...
IntPtr hinstance;
Vk.Result result = Vk.CreateInstance(instance_create_info, IntPtr.Zero, out hinstance); <-- error AccessViolationException
I don't understand where is my problem, because it seems to be a valid solution : StackOverflow : AccessViolationException when calling vkEnumeratePhysicalDevices via pInvoke from c#.
I tried by initializing my IntPtr hinstance with
Marshal.AllocHGlobal(Marshal.SizeOf<Vk.Instance>());
I also tried to "convert" my instance_create_info to another IntPtr with Marshal.StructureToPtr(...); and I tried to pass instance_create_info and instance by the ref keyword. Obviously, nothing worked.
Any idea ?
EDIT :
The native function is used as follows :
//Definition
typedef struct VkApplicationInfo {
VkStructureType sType;
const void* pNext;
const char* pApplicationName;
uint32_t applicationVersion;
const char* pEngineName;
uint32_t engineVersion;
uint32_t apiVersion;
} VkApplicationInfo;`
typedef struct VkInstanceCreateInfo {
VkStructureType sType;
const void* pNext;
VkInstanceCreateFlags flags;
const VkApplicationInfo* pApplicationInfo;
uint32_t enabledLayerCount;
const char* const* ppEnabledLayerNames;
uint32_t enabledExtensionCount;
const char* const* ppEnabledExtensionNames;
} VkInstanceCreateInfo;
#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
VK_DEFINE_HANDLE(VkInstance)
//Code
VkApplicationInfo application_info{};
application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application_info.apiVersion = VK_API_VERSION;
application_info.applicationVersion = VK_MAKE_VERSION( 1, 0, 0 );
application_info.pApplicationName = "";
application_info.engineVersion = VK_MAKE_VERSION( 1, 0, 0 );
application_info.pEngineName = "";
VkInstanceCreateInfo instance_create_info{};
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pApplicationInfo = &application_info;
instance_create_info.enabledLayerCount = 0
instance_create_info.ppEnabledLayerNames = nullptr
instance_create_info.enabledExtensionCount = 0
instance_create_info.ppEnabledExtensionNames = nullptr
VkInstance _instance = nullptr;
assert( !vkCreateInstance( &instance_create_info, nullptr, &_instance ) );
it seems that you are trying to do something similar this guy made a wrapper for the c#. Can be useful for you. Go to Source/SharpVulkan/Generated/Functions.
public static unsafe Instance CreateInstance(ref InstanceCreateInfo createInfo, AllocationCallbacks* allocator = null)
{
Instance instance;
fixed (InstanceCreateInfo* __createInfo__ = &createInfo)
{
vkCreateInstance(__createInfo__, allocator, &instance).CheckError();
}
return instance;
}
[DllImport("vulkan-1.dll", CallingConvention = CallingConvention.StdCall)]
internal static extern unsafe Result vkCreateInstance(InstanceCreateInfo* createInfo, AllocationCallbacks* allocator, Instance* instance);
internal static unsafe void EnumerateInstanceExtensionProperties(byte* layerName, ref uint propertyCount, ExtensionProperties* properties)
{
fixed (uint* __propertyCount__ = &propertyCount)
{
vkEnumerateInstanceExtensionProperties(layerName, __propertyCount__, properties).CheckError();
}
}
Hope it is helpfull :)
remove out keyword for third argument or declare it as corresponding struct.
Related
I'm trying to Marshal the following structures from c++ to c# on a Windows CE program and compact framework 2.0. I'm having a lot of difficulties with the marshalling of strings.
I have this c++ code:
#define Console_Parameters_MAX 50
struct AllParameters {
Parameter Parameters[Console_Parameters_MAX];
} ;
struct Parameter {
int Index;
int Id;
char Value[20];
};
extern "C" {
BOOL GetAllConsoleParameters(AllParameters *pItem);
}
and this are the corresponding c# code:
[StructLayout(LayoutKind.Sequential)]
public struct AllParameters {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
public Parameter[] Parameters
}
[StructLayout(LayoutKind.Sequential)]
public struct Parameter {
public int Index;
public int Id;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public byte[] Value;
}
[DllImport("exemple.dll", SetLastError = true)]
public static extern bool GetAllConsoleParameters([MarshalAs(UnmanagedType.Struct)] ref AllParameters pItem);
and this is how I invoke it:
AllParameters item = new AllParameters();
if (AppAPI.GetAllConsoleParameters(ref item)) {
var array = item.Parameters;
}
When I call the GetAllConsoleParameters I get exception NotSupportedException. I've tried many configurations but with no success.
Can anyone advise on how to achieve it?
Thanks in advance
This works for me on a Windows Desktop. You might have to change the calling convention to Cdecl in the C DLL and the C# DllImport attribute, because I read here that Cdecl is standard on Windows CE: https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.callingconvention(v=vs.110).aspx
C code:
extern "C" {
__declspec(dllexport) BOOL __stdcall GetAllConsoleParameters(AllParameters *pItem)
{
pItem->Parameters[0].Index = 0;
pItem->Parameters[0].Id = 42;
CopyMemory(&pItem->Parameters[0].Value[0], "Hello World", 12);
pItem->Parameters[1].Index = 1;
pItem->Parameters[1].Id = 43;
CopyMemory(&pItem->Parameters[1].Value[0], "Hello World 43", 15);
return TRUE;
}
}
C# code:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct Parameter
{
int Index;
int Id;
//char Value[20];
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
string Value;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct AllParameters
{
//Parameter Parameters[Console_Parameters_MAX];
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
Parameter[] Parameters;
};
class Program
{
[DllImport("MarshalC.dll", CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetAllConsoleParameters(ref AllParameters pItem);
static void Main(string[] args)
{
var size = Marshal.SizeOf<AllParameters>();
AllParameters all = new AllParameters();
bool result = GetAllConsoleParameters(ref all);
}
}
I would do it like this
[StructLayout(LayoutKind.Sequential)]
public struct AllParameters {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
public Parameter[] Parameters;
}
[StructLayout(LayoutKind.Sequential)]
public struct Parameter {
public int Index;
public int Id;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] Value;
}
[DllImport("exemple.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern bool GetAllConsoleParameters(ref IntPtr pItem);
static void Main(string[] args)
{
AllParameters allParameters = new AllParameters();
allParameters.Parameters = new Parameter[50];
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(allParameters));
int z = Marshal.SizeOf(allParameters);
if (GetAllConsoleParameters(ref ptr))
{
Marshal.PtrToStructure(ptr, allParameters);
Parameter[] parameters = allParameters.Parameters;
}
}
following my solution, c++ code:
/* - not used
#define Console_Parameters_MAX 50
struct AllParameters {
Parameter Parameters[Console_Parameters_MAX];
} ;
*/
struct Parameter {
int Index;
int Id;
char Value[20];
};
extern "C" {
BOOL GetAllConsoleParameters(Parameter pItem[], int size);
}
and corresponding c# code:
/* - not used
[StructLayout(LayoutKind.Sequential)]
public struct AllParameters {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
public Parameter[] Parameters
}
*/
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct Parameter {
public int Index;
public int Id;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] Value;
}
[DllImport("exemple.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetAllConsoleParameters([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1), Out] ConsoleParameter[] myStruct, int size);
and code invoke:
ConsoleParameter[] item = new ConsoleParameter[50];
if (AppAPI.GetAllConsoleParameters(item, 50)) {
var array = item;
}
thanks a lot for help
I have the keen idea to write a wrapper for Vulkan in c#.
Unfortunately the second call of the Vulkan API already fails unexplainably.
The code below is taken from Sascha Willems' Vulkan examples and converted into c# code:
Vk.ApplicationInfo applicationInfo = new Vk.ApplicationInfo();
applicationInfo.sType = Vk.StructureType.STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo.pApplicationName = "Example";
applicationInfo.pEngineName = "Example";
applicationInfo.apiVersion = (uint)Math.Pow(2, 22) + 2;
string[] enabledExtensions = new string[] { "VK_KHR_surface", "VK_KHR_win32_surface" };
Vk.InstanceCreateInfo instanceCreateInfo = new Vk.InstanceCreateInfo();
instanceCreateInfo.sType = Vk.StructureType.STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo.pNext = null;
instanceCreateInfo.pApplicationInfo = applicationInfo;
instanceCreateInfo.enabledExtensionCount = (uint)enabledExtensions.Count();
instanceCreateInfo.ppEnabledExtensionNames = enabledExtensions;
Vk.Instance theInstance = new Vk.Instance();
Vk.Result vr = Vk.vkCreateInstance(instanceCreateInfo, IntPtr.Zero, theInstance);
// vr = SUCCESS
uint gpuCount = 0;
vr = Vk.vkEnumeratePhysicalDevices(theInstance, ref gpuCount, IntPtr.Zero);
//Fails with System.AccessViolationException
with
public static class Vk
{
public enum Result
{
SUCCESS = 0,
...
};
public enum StructureType
{
...
}
static Vk()
{
List<string> path = new List<string>() { #"C:\VulkanSDK\1.0.3.1\Source\lib32\" };
AddEnvironmentPaths(path);
}
static void AddEnvironmentPaths(IEnumerable<string> paths)
{
var path = new[] { Environment.GetEnvironmentVariable("PATH") ?? string.Empty };
string newPath = string.Join(Path.PathSeparator.ToString(), path.Concat(paths));
Environment.SetEnvironmentVariable("PATH", newPath);
}
[DllImport("vulkan-1.dll")]
public static extern Result vkCreateInstance(InstanceCreateInfo instanceCreateInfo, IntPtr pAllocator, Instance instance);
[StructLayout(LayoutKind.Sequential)]
public class InstanceCreateInfo
{
public StructureType sType;
public object pNext;
public uint flags;
public ApplicationInfo pApplicationInfo;
public uint enabledLayerCount;
public string[] ppEnabledLayerNames;
public uint enabledExtensionCount;
public string[] ppEnabledExtensionNames;
}
[StructLayout(LayoutKind.Sequential)]
public class ApplicationInfo
{
public StructureType sType;
public object pNext;
public string pApplicationName;
public uint applicationVersion;
public string pEngineName;
public uint engineVersion;
public uint apiVersion;
}
[StructLayout(LayoutKind.Sequential)]
public class Instance
{
}
[DllImport("vulkan-1.dll")]
public static extern Result vkEnumeratePhysicalDevices(Instance instance, ref uint pPhysicalDeviceCount, PhysicalDevice pPhysicalDevices);
[DllImport("vulkan-1.dll")]
public static extern Result vkEnumeratePhysicalDevices(Instance instance, ref uint pPhysicalDeviceCount, IntPtr pPhysicalDevices);
public class PhysicalDevice
{
}
}
My Suspicion is that Vk.Instance should be something else than just an empty class. VkInstance is in the official vulkan.h defined as typedef struct VkInstance_T* VkInstance. My understanding of this line is unfortunately very limited. I already tried exchanging the type Vk.Instance with IntPtr and object but without success.
Theses are important segments from vulkan.h
#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
VK_DEFINE_HANDLE(VkInstance)
VK_DEFINE_HANDLE(VkPhysicalDevice)
VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance);
VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(
VkInstance instance,
uint32_t* pPhysicalDeviceCount,
VkPhysicalDevice* pPhysicalDevices);
Zastai answered my question.
It had to be
[DllImport("vulkan-1.dll")]
public static extern Result vkCreateInstance(InstanceCreateInfo instanceCreateInfo, IntPtr pAllocator, out IntPtr instance);
because it were indeed pointers to pointers.
IntPtr instance;
Vk.Result vr = Vk.vkCreateInstance(instanceCreateInfo, IntPtr.Zero, out instance);
uint gpuCount = 0;
vr = Vk.vkEnumeratePhysicalDevices(instance, ref gpuCount, IntPtr.Zero);
Thanks a lot!
Structure 1:
typedef struct _wfs_cdm_cu_info
{
USHORT usTellerID;
USHORT usCount;
LPWFSCDMCASHUNIT * lppList;
} WFSCDMCUINFO, * LPWFSCDMCUINFO;
Structure 2:
typedef struct _wfs_cdm_cashunit
{
USHORT usNumber;
USHORT usType;
LPSTR lpszCashUnitName;
CHAR cUnitID[5];
CHAR cCurrencyID[3];
ULONG ulValues;
ULONG ulInitialCount;
ULONG ulCount;
ULONG ulRejectCount;
ULONG ulMinimum;
ULONG ulMaximum;
BOOL bAppLock;
USHORT usStatus;
USHORT usNumPhysicalCUs;
LPWFSCDMPHCU * lppPhysical;
} WFSCDMCASHUNIT, * LPWFSCDMCASHUNIT;
Structure 3:
typedef struct _wfs_cdm_physicalcu
{
LPSTR lpPhysicalPositionName;
CHAR cUnitID[5];
ULONG ulInitialCount;
ULONG ulCount;
ULONG ulRejectCount;
ULONG ulMaximum;
USHORT usPStatus;
BOOL bHardwareSensor;
} WFSCDMPHCU, * LPWFSCDMPHCU;
C# structure:-
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet=System.Runtime.InteropServices.CharSet.Ansi, Pack = 1)]
public struct WFSCDMPHCU { [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
public string lpPhysicalPositionName;[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst=5)]
public string cUnitID;
public uint ulInitialCount;
public uint ulCount;
public uint ulRejectCount;
public uint ulMaximum;
public ushort usPStatus;
public int bHardwareSensor;
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet=System.Runtime.InteropServices.CharSet.Ansi, Pack = 1)]
public struct WFSCDMCASHUNIT {
public ushort usNumber;
public ushort usType; [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
public string lpszCashUnitName;[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst=5)]
public string cUnitID; [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst=3)]
public string cCurrencyID;
public uint ulValues;
public uint ulInitialCount;
public uint ulCount;
public uint ulRejectCount;
public uint ulMinimum;
public uint ulMaximum;
public int bAppLock;
public ushort usStatus;
public ushort usNumPhysicalCUs;
public System.IntPtr lppPhysical;
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
public struct WFSCDMCUINFO {
public ushort usTellerID;
public ushort usCount;
public System.IntPtr lppList;
}
DLLImport
[DllImport(#"Dispenser.dll")]
public static extern int CDM_SetCashUnit(out WFSCDMCUINFO cuinfo);
1)My main problem is How should I marshall or allocate memory for this structure to send data from C# to C++ ,the second and third structure being array of structure???
2)If I use pointer how efficient it would be.
3)If C++/CLI wrapper be used then how I could access it through C#.
It is been a long time I'm working and I m yet to figure out how should I fill the array of structures in C# .
Follwing code is what i try to figure out...
Marshal Code:
Facing an error of "to define an extension non generic static class"
public static IntPtr GetIntPtr(this object obj) {
try {
var handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
var thread = new Thread(() => {
Thread.Sleep(20000);
handle.Free();
});
thread.Start();
return handle.AddrOfPinnedObject();
} catch (ArgumentException) {
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(obj));
Marshal.StructureToPtr(obj, ptr, false);
return ptr;
}
}
public static T FromIntPtr<T>(this IntPtr ptr) {
if (ptr == IntPtr.Zero)
return default(T);
return (T) Marshal.PtrToStructure(ptr, typeof (T));
}
A link of C++ code, of how I have called the function is given.link
This is my structure mapping in c#:
[StructLayout(LayoutKind.Sequential)]
public struct Message
{
public uint MsgId;
public uint DLC;
public uint Interval;
public uint Handle;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public byte[] Data;
};
C++ here:
struct Message {
unsigned int MsgId;
unsigned int DLC;
unsigned int Interval;
unsigned int Handle;
unsigned char Data[64];
} ;
My method in c++ which needs such a structure as a parameter:
extern "C" __declspec(dllexport) int _stdcall MessageWrapper( Message *msg)
Here is how i call this method from C#:
Message frame = new Message();
frame.MsgId = (uint)MsgId;
frame.DLC = (uint)Dlc;
frame.Interval = (uint)Interval;
frame.Data = new byte[64];
int rawsize = Marshal.SizeOf(frame);
IntPtr frameBuffer = Marshal.AllocHGlobal(rawsize);
Marshal.StructureToPtr(frame, frameBuffer, false);
Call the method here:
int response = HwWrapper.MessageWrapper(frameBuffer);
You may have some modifications made in c++ and you can read them back here in c#:
frame = (Message)(Marshal.PtrToStructure(frameBuffer, typeof(Message)));
Marshal.FreeHGlobal(frameBuffer);
I've got a c dll that contains an exposed function, that takes three parameters :
int ParseInput(char* opt_path, char* input, SENNA_RESULT_ARRAY* result);
I want to call this from C#, which actually works. The problem is that the result struct is not affected.
Here is the structure defined in c code :
typedef struct RESULT_
{
char* word;
int pos_start;
int pos_end;
char* pos;
char* chk;
char* ner;
char* psg;
} RESULT;
typedef struct RESULT_ARRAY_
{
int size;
RESULT* Results;
} RESULT_ARRAY;
and my c# code :
[StructLayout(LayoutKind.Sequential)]
public struct SENNA_RESULT
{
[MarshalAs(UnmanagedType.LPStr)]
public string word;
[MarshalAs(UnmanagedType.I4)]
public int pos_start;
[MarshalAs(UnmanagedType.I4)]
public int pos_end;
[MarshalAs(UnmanagedType.LPStr)]
public string pos;
[MarshalAs(UnmanagedType.LPStr)]
public string chk;
[MarshalAs(UnmanagedType.LPStr)]
public string ner;
[MarshalAs(UnmanagedType.LPStr)]
public string psg;
}
[StructLayout(LayoutKind.Sequential)]
public struct SENNA_RESULT_ARRAY
{
public SENNA_RESULT[] Results;
public int size;
}
[DllImport("Senna-32.dll", CharSet = CharSet.Ansi)]
static extern int Parse(string msg, string stream, ref SENNA_RESULT_ARRAY results);
Parse(#"path", "sentence", ref result_array)
I've tried many things, like :
1-use classes instead of struct without ref keyword
2-use a pointer instead of passing a struct
Each time i got a different error like
array is not of the specified type
low level error( corrupted heap)
even if i don't specify the array in the first struct, the size member has not the correct value (the C code prints the value in the console)
Any advice ?
Thanks
Consider using code below.
[StructLayout(LayoutKind.Sequential)]
public struct SENNA_RESULT
{
public IntPtr word;
public int pos_start;
public int pos_end;
public IntPtr pos;
public IntPtr chk;
public IntPtr ner;
public IntPtr psg;
}
[StructLayout(LayoutKind.Sequential)]
public struct SENNA_RESULT_ARRAY
{
public IntPtr Results;
public int size;
}
[DllImport("Senna-32.dll")]
static extern int Parse(string msg, string stream, out SENNA_RESULT_ARRAY results);
Here is usage example
SENNA_RESULT_ARRAY array = new SENNA_RESULT_ARRAY();
int result = Parse("path", "sentence", out array);
if (result == SUCCESS && array.Results != IntPtr.Zero)
{
for (int index = 0; index < array.size; index++)
{
IntPtr offset = (IntPtr)((int)array.Results + index * Marshal.SizeOf(typeof(SENNA_RESULT)));
SENNA_RESULT senna = (SENNA_RESULT)Marshal.PtrToStructure(offset, typeof(SENNA_RESULT));
}
}
I hope that you understand that it is just idea. Make sure that code is working properly and then improve it to make it more comfotable to use. I talking about replacing IntPtr with string.
The following is a complete program. It works fine as long as you don't uncomment the '#define BROKEN' at the top. The break is due to a PInvoke failing to marshal a union correctly. The INPUT_RECORD structure in question has a number of substructures that might be used depending on the value in EventType.
What I don't understand is that when I define only the single child structure of KEY_EVENT_RECORD it works with the explicit declaration at offset 4. But when I add the other structures at the same offset the structure's content get's totally hosed.
//UNCOMMENT THIS LINE TO BREAK IT:
//#define BROKEN
using System;
using System.Runtime.InteropServices;
class ConIOBroken
{
static void Main()
{
int nRead = 0;
IntPtr handle = GetStdHandle(-10 /*STD_INPUT_HANDLE*/);
Console.Write("Press the letter: 'a': ");
INPUT_RECORD record = new INPUT_RECORD();
do
{
ReadConsoleInputW(handle, ref record, 1, ref nRead);
} while (record.EventType != 0x0001/*KEY_EVENT*/);
Assert.AreEqual((short)0x0001, record.EventType);
Assert.AreEqual(true, record.KeyEvent.bKeyDown);
Assert.AreEqual(0x00000000, record.KeyEvent.dwControlKeyState & ~0x00000020);//strip num-lock and test
Assert.AreEqual('a', record.KeyEvent.UnicodeChar);
Assert.AreEqual((short)0x0001, record.KeyEvent.wRepeatCount);
Assert.AreEqual((short)0x0041, record.KeyEvent.wVirtualKeyCode);
Assert.AreEqual((short)0x001e, record.KeyEvent.wVirtualScanCode);
}
static class Assert { public static void AreEqual(object x, object y) { if (!x.Equals(y)) throw new ApplicationException(); } }
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ReadConsoleInputW(IntPtr hConsoleInput, ref INPUT_RECORD lpBuffer, int nLength, ref int lpNumberOfEventsRead);
[StructLayout(LayoutKind.Explicit)]
public struct INPUT_RECORD
{
[FieldOffset(0)]
public short EventType;
//union {
[FieldOffset(4)]
public KEY_EVENT_RECORD KeyEvent;
#if BROKEN
[FieldOffset(4)]
public MOUSE_EVENT_RECORD MouseEvent;
[FieldOffset(4)]
public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
[FieldOffset(4)]
public MENU_EVENT_RECORD MenuEvent;
[FieldOffset(4)]
public FOCUS_EVENT_RECORD FocusEvent;
//}
#endif
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_EVENT_RECORD
{
public bool bKeyDown;
public short wRepeatCount;
public short wVirtualKeyCode;
public short wVirtualScanCode;
public char UnicodeChar;
public int dwControlKeyState;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSE_EVENT_RECORD
{
public COORD dwMousePosition;
public int dwButtonState;
public int dwControlKeyState;
public int dwEventFlags;
};
[StructLayout(LayoutKind.Sequential)]
public struct WINDOW_BUFFER_SIZE_RECORD
{
public COORD dwSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct MENU_EVENT_RECORD
{
public int dwCommandId;
}
[StructLayout(LayoutKind.Sequential)]
public struct FOCUS_EVENT_RECORD
{
public bool bSetFocus;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}
}
UPDATE:
For those worried about the struct declarations themselves:
bool is treated as a 32-bit value
the reason for offset(4) on the data is to allow for the 32-bit structure alignment which prevents the union from beginning at offset 2.
Again, my problem isn't making PInvoke work at all, it's trying to figure out why these additional structures (supposedly at the same offset) are fowling up the data by simply adding them.
I believe it will work if you make bSetFocus and dwCommandId of type uint.
In the future, check out the PInvoke wiki for the proper signatures. It's usually accurate or, at the very least, a good starting point.
//UNCOMMENT THIS LINE TO BREAK IT:
//#define BROKEN
using System;
using System.Runtime.InteropServices;
class ConIOBroken
{
static void Main()
{
int nRead = 0;
IntPtr handle = GetStdHandle(-10 /STD_INPUT_HANDLE/);
Console.Write("Press the letter: 'a': ");
INPUT_RECORD record = new INPUT_RECORD();
do
{
ReadConsoleInputW(handle, ref record, 1, ref nRead);
} while (record.EventType != 0x0001/*KEY_EVENT*/);
Assert.AreEqual((short)0x0001, record.EventType);
Assert.AreEqual(1u, record.KeyEvent.bKeyDown);
Assert.AreEqual(0x00000000, record.KeyEvent.dwControlKeyState & ~0x00000020);//strip num-lock and test
Assert.AreEqual('a', record.KeyEvent.UnicodeChar);
Assert.AreEqual((short)0x0001, record.KeyEvent.wRepeatCount);
Assert.AreEqual((short)0x0041, record.KeyEvent.wVirtualKeyCode);
Assert.AreEqual((short)0x001e, record.KeyEvent.wVirtualScanCode);
return;
}
static class Assert { public static void AreEqual(object x, object y) { if (!x.Equals(y)) throw new ApplicationException(); } }
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ReadConsoleInputW(IntPtr hConsoleInput, ref INPUT_RECORD lpBuffer, int nLength, ref int lpNumberOfEventsRead);
[StructLayout(LayoutKind.Explicit)]
public struct INPUT_RECORD
{
[FieldOffset(0)]
public short EventType;
//union {
[FieldOffset(4)]
public KEY_EVENT_RECORD KeyEvent;
[FieldOffset(4)]
public MOUSE_EVENT_RECORD MouseEvent;
[FieldOffset(4)]
public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
[FieldOffset(4)]
public MENU_EVENT_RECORD MenuEvent;
[FieldOffset(4)]
public FOCUS_EVENT_RECORD FocusEvent;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_EVENT_RECORD
{
public uint bKeyDown;
public short wRepeatCount;
public short wVirtualKeyCode;
public short wVirtualScanCode;
public char UnicodeChar;
public int dwControlKeyState;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSE_EVENT_RECORD
{
public COORD dwMousePosition;
public int dwButtonState;
public int dwControlKeyState;
public int dwEventFlags;
};
[StructLayout(LayoutKind.Sequential)]
public struct WINDOW_BUFFER_SIZE_RECORD
{
public COORD dwSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct MENU_EVENT_RECORD
{
public int dwCommandId;
}
[StructLayout(LayoutKind.Sequential)]
public struct FOCUS_EVENT_RECORD
{
public uint bSetFocus;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}
}
1) public bool bKeyDown should be Int32 bKeyDown because it's a BOOL (4bytes) in c++
2) public bool bSetFocus should be Int32
Just a thought, what happens if you declare the largest field last? Maybe P/Invoke is copying up to the last field, which ends before earlier fields.
Try adding a manually calculated Size field to the StructLayout attribute, like this:
[StructLayout(LayoutKind.Explicit, Size=...)]
The original code with the bool contained 13 bytes, starting at FieldOffset(4) ...
The MOUSE_EVENT_RECORD starting at the same offset conatianed 16 bytes starting at the same offset.
When you changed the bool (1byte) to an uint(4bytes) you made up the 3 bytes.