I am trying to use the WriteConsoleOutputfunction from kernel32.dll, however I cannot get unicode characters to display correctly, they always display as the wrong characters.
I have attempted to use:
Console.OutputEncoding = System.Text.Encoding.UTF8;
Changing this to Encoding.Unicode does not work either.
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleOutputCP(uint wCodePageID);
public void SetCP(){
SetConsoleOutputCP(65001);
}
I have tried using both of the above, each one individually and none with just about every combination of values.
I have also switched between all fonts (including the true type ones), however none of them seem to display the characters correctly.
Here is the code I am using to use WriteConsoleOutput
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "WriteConsoleOutputW", CharSet = CharSet.Unicode)]
static extern bool WriteConsoleOutputW(SafeFileHandle hConsoleOutput, CharInfo[] lpBuffer, Coord dwBufferSize, Coord dwBufferCoord, ref SmallRect lpWriteRegion);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern SafeFileHandle CreateFile(string fileName, [MarshalAs(UnmanagedType.U4)] uint fileAccess, [MarshalAs(UnmanagedType.U4)] uint fileShare, IntPtr securityAttributes, [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, [MarshalAs(UnmanagedType.U4)] int flags, IntPtr template);
private static readonly SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
public static void RegionWrite(string s, int x, int y, int width, int height)
{
if (!h.IsInvalid)
{
int length = width * height;
// Pad any extra space we have
string fill = s + new string(' ', length - s.Length);
// Grab the background and foreground as integers
int bg = (int) Console.BackgroundColor;
int fg = (int) Console.ForegroundColor;
// Make background and foreground into attribute value
short attr = (short)(fg | (bg << 4));
CharInfo[] buf = fill.Select(c =>
{
CharInfo info = new CharInfo();
// Give it our character to write
info.Char.UnicodeChar = c;
// Use our attributes
info.Attributes = attr;
// Return info for this character
return info;
}).ToArray();
// Make everything short so we don't have to cast all the time
short sx = (short) x;
short sy = (short) y;
short swidth = (short) width;
short sheight = (short) height;
// Make a buffer size out our dimensions
Coord bufferSize = new Coord(swidth, sheight);
// Not really sure what this is but its probably important
Coord pos = new Coord(0, 0);
// Where do we place this?
SmallRect rect = new SmallRect() { Left = sx, Top = sy, Right = (short) (sx + swidth), Bottom = (short) (sy + sheight) };
bool b = WriteConsoleOutputW(h, buf, bufferSize, pos, ref rect);
}
else
{
throw new Exception("Console handle is invalid.");
}
}
Using this with standard ASCII characters works perfectly:
RegionWrite("Hello world", 4, 4, 10, 10);
However when I use anything above the standard ASCII range, it fails to display correctly:
RegionWrite("┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬", 4, 4, 10, 10); This outputs as two lines of ',' characters, this makes some sense as the "┬" character has a value of 9516, 9516 % 128 is 44 which is the ascii code for ','.
I know it is physically possible to output these characters as Console.Write("┬┬┬┬") works correctly. I am switching from Console.Write to WriteConsoleOutput as there is a significant performance increase.
Here is the code im using to set code pages:
public void Setup()
{
Console.BufferHeight = Console.WindowHeight;
Console.BufferWidth = Console.WindowWidth;
Console.OutputEncoding = System.Text.Encoding.UTF8;
SetConsoleOutputCP(65001);
DefaultColor();
Console.Clear();
Console.ReadLine();
RegionWrite("┬┬┬┬", 4, 4, 10, 10);
Console.WriteLine("┬┬┬┬");
Console.ReadLine();
}
Here are my structures:
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
public short X;
public short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
}
[StructLayout(LayoutKind.Explicit)]
public struct CharUnion
{
[FieldOffset(0)] public char UnicodeChar;
[FieldOffset(0)] public byte AsciiChar;
}
[StructLayout(LayoutKind.Explicit)]
public struct CharInfo
{
[FieldOffset(0)] public CharUnion Char;
[FieldOffset(2)] public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
I assume I have screwed up one of the variables of WriteConsoleOutput but after hours of searching for answers i'm really not sure where i've gone wrong. Is there some internal set encoding function I need to use?
nvm fixed it
Simple solution, change
[StructLayout(LayoutKind.Explicit)]
public struct CharUnion
{
[FieldOffset(0)] public char UnicodeChar;
[FieldOffset(0)] public byte AsciiChar;
}
to
[StructLayout(LayoutKind.Explicit, CharSet=CharSet.Unicode)]
public struct CharUnion
{
[FieldOffset(0)] public char UnicodeChar;
[FieldOffset(0)] public byte AsciiChar;
}
This is because it will default to ANSI meaning your unicode characters get automatically turned into ANSI, hence ┬ into ,
Related
I want to use a c++ dll in c#. I'm using [DllImport] to call the method. I'm having trouble passing struct to a method.
I have a C struct:
typedef struct
{
DWORD TopPoint;
DWORD EndPoint;
WORD dwCount;
MYFUNC_NUMERIC11 *pGetData;
} MYFUNC_BUFFERNORMAL;
MYFUNC_NYMERIC11 is another struct.
typedef struct
{
BYTE Sign; // Sign ("±")
BYTE Integer[3]; // 3-digit integer (no zero suppression)
BYTE Period; // Decimal point (".")
BYTE Decimal[6]; // 6-digit decimal number
} MYFUNC_NUMERIC11;
I have written a C# struct to mimic this.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public unsafe struct MYFUNC_BUFFERNORMAL
{
public uint TopPoint;
public uint EndPoint;
public ushort Count;
public IntPtr pGetData;
}
A pointer to the struct is an argument in a method. C# function is:
[DllImport("MYFUNC_DLL.dll", EntryPoint = "MYFUNC_GetData", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall, ThrowOnUnmappableChar = true)]
public static extern int MYFUNC_GetData(IntPtr myfuncHandle, UInt32 dwIO, ref IntPtr pBufferNormal, Byte bccFlg);
This is the method in C:
MYFUNC_STATUS MYFUNC_GetData(MYFUNC_HANDLE myfuncHandle, DWORD dwOut, MYFUNC_BUFFERNORMAL *pBufferNormal , BYTE bccFlg)
The return type is cast to an enum, which has an interpretation. The struct parameter is invalid. I've tried to allocate memory using Marshal.AllocHGlobal(...), but the parameter is still invalid, i.e. there is no error during compilation but the value returned is incorrect.
I've spent quite a few hours on this, still unable to figure out what to do. A lot of similar questions exist already, like here: How do I convert c struct from dll to C# or here: How to pass C# array to C++ and return it back to C# with additional items?, but I, somehow, still haven't figured out a way.
Something like this should work, at least with one element in the array (is it an array?). For an array, you will have to allocate sizeof * count of elements and marshal (StructureToPtr) each element at its offset.
var num = new MYFUNC_NUMERIC11();
num.Integer = new byte[] { 1, 2, 3 };
num.Decimal = new byte[] { 4, 5, 6, 7, 8, 9 };
num.Sign = 10;
num.Period = 11;
var buffer = new MYFUNC_BUFFERNORMAL();
buffer.Count = 1234;
buffer.EndPoint = 5678;
buffer.TopPoint = 9;
buffer.pGetData = Marshal.AllocCoTaskMem(Marshal.SizeOf(num));
try
{
Marshal.StructureToPtr(num, buffer.pGetData, false);
MYFUNC_GetData(Whatever, 0, ref buffer, 0);
}
finally
{
Marshal.FreeCoTaskMem(buffer.pGetData);
}
With these definitions.
[StructLayout(LayoutKind.Sequential)]
public struct MYFUNC_BUFFERNORMAL
{
public uint TopPoint;
public uint EndPoint;
public ushort Count;
public IntPtr pGetData;
}
[StructLayout(LayoutKind.Sequential)]
public struct MYFUNC_NUMERIC11
{
public byte Sign;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] Integer;
public byte Period;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] Decimal;
}
// check calling convention
[DllImport(#"MYFUNC_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern MYFUNC_STATUS MYFUNC_GetData(IntPtr myfuncHandle, uint dwIO, ref MYFUNC_BUFFERNORMAL pBufferNormal, byte bccFlg);
I need to read text from a particular location in the console, say 5,5.
If I were to need to write to this location, it would simply be:
Console.SetCursorPosition(5, 5);
Console.Write("My text");
Is there any way i can read in a similar way?
Just to clarify:
I don't want to stop to take an input from the user, there's a chance even that the input won't be from the user, but something previously printed out. I literally want some sort of:
Console.GetCharAtLocation(5,5) or something similar.
Here is a C# code utility that can read what's currently in the Console buffer (not the window, the buffer):
Sample usage:
class Program
{
static void Main(string[] args)
{
// read 10 lines from the top of the console buffer
foreach (string line in ConsoleReader.ReadFromBuffer(0, 0, (short)Console.BufferWidth, 10))
{
Console.Write(line);
}
}
}
Utility:
public class ConsoleReader
{
public static IEnumerable<string> ReadFromBuffer(short x, short y, short width, short height)
{
IntPtr buffer = Marshal.AllocHGlobal(width * height * Marshal.SizeOf(typeof(CHAR_INFO)));
if (buffer == null)
throw new OutOfMemoryException();
try
{
COORD coord = new COORD();
SMALL_RECT rc = new SMALL_RECT();
rc.Left = x;
rc.Top = y;
rc.Right = (short)(x + width - 1);
rc.Bottom = (short)(y + height - 1);
COORD size = new COORD();
size.X = width;
size.Y = height;
const int STD_OUTPUT_HANDLE = -11;
if (!ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), buffer, size, coord, ref rc))
{
// 'Not enough storage is available to process this command' may be raised for buffer size > 64K (see ReadConsoleOutput doc.)
throw new Win32Exception(Marshal.GetLastWin32Error());
}
IntPtr ptr = buffer;
for (int h = 0; h < height; h++)
{
StringBuilder sb = new StringBuilder();
for (int w = 0; w < width; w++)
{
CHAR_INFO ci = (CHAR_INFO)Marshal.PtrToStructure(ptr, typeof(CHAR_INFO));
char[] chars = Console.OutputEncoding.GetChars(ci.charData);
sb.Append(chars[0]);
ptr += Marshal.SizeOf(typeof(CHAR_INFO));
}
yield return sb.ToString();
}
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct CHAR_INFO
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public byte[] charData;
public short attributes;
}
[StructLayout(LayoutKind.Sequential)]
private struct COORD
{
public short X;
public short Y;
}
[StructLayout(LayoutKind.Sequential)]
private struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct CONSOLE_SCREEN_BUFFER_INFO
{
public COORD dwSize;
public COORD dwCursorPosition;
public short wAttributes;
public SMALL_RECT srWindow;
public COORD dwMaximumWindowSize;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadConsoleOutput(IntPtr hConsoleOutput, IntPtr lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, ref SMALL_RECT lpReadRegion);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
}
A simplified demo that works in Windows 10 for reading a single character from the specified (X, Y) position on the screen. Tested with .NET 4.7.2.
First, here's a line of code which populates the Console with a demo grid. Note that it should render in the top left corner of your screen in order for the demo to work.
static void Populate_Console()
{
Console.Clear();
Console.Write(#"
┌───────┐
1│C D E F│
2│G H I J│
3│K L M N│
4│O P Q R│
└───────┘
2 4 6 8 ".Trim());
}
It should look like this:
Now let's read some characters back. To start you'll need the native console handle for stdout. Here's the P/Invoke method for getting it from Win32:
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr GetStdHandle(int num);
Now for the cool part; this seems to be the only answer on this page so far which uses the ReadConsoleOutputCharacter Win32 function. Although it doesn't let you get the character color attributes, this approach does save all the trouble of dealing with copy rectangles and having to use CreateConsoleScreenBuffer to allocate screen buffers and copy between them.
There are separate Ansi and Unicode versions, and you need to call the proper one depending on the code page that's active in the Console window. I show both P/Invoke signatures here, but for simplicity, in the example I'll just continue with the Ansi version:
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.Bool)] // ̲┌──────────────────^
static extern bool ReadConsoleOutputCharacterA(
IntPtr hStdout, // result of 'GetStdHandle(-11)'
out byte ch, // A̲N̲S̲I̲ character result
uint c_in, // (set to '1')
uint coord_XY, // screen location to read, X:loword, Y:hiword
out uint c_out); // (unwanted, discard)
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)] // ̲┌───────────────────^
static extern bool ReadConsoleOutputCharacterW(
IntPtr hStdout, // result of 'GetStdHandle(-11)'
out Char ch, // U̲n̲i̲c̲o̲d̲e̲ character result
uint c_in, // (set to '1')
uint coord_XY, // screen location to read, X:loword, Y:hiword
out uint c_out); // (unwanted, discard)
You may notice I've stripped down the marshaling on these to the bare minimum needed for the purposes of my example code, which is designed to only fetch one character at a time. Therefore, you will probably find that c_in must always be 1, due to the managed pointer declarations ‘out byte ch’ and ‘out Char ch’.
That's really all you need; calling the appropriate P/Invoke function as described above is mostly self-explanatory if you limit yourself to reading a single character. To show this with a trivial example, I'll finish with the cute demo program, that reads four characters back from the Console, along a diagonal of the grid we drew above.
static void Windows_Console_Readback()
{
var stdout = GetStdHandle(-11);
for (uint coord, y = 1; y <= 4; y++)
{
coord = (5 - y) * 2; // loword <-- X coord to read
coord |= y << 16; // hiword <-- Y coord to read
if (!ReadConsoleOutputCharacterA(
stdout,
out byte chAnsi, // result: single ANSI char
1, // # of chars to read
coord, // (X,Y) screen location to read (see above)
out _)) // result: actual # of chars (unwanted)
throw new Win32Exception();
Console.Write(" " + (Char)chAnsi + " ");
}
}
And there you have it...
This functionality doesn't exist. It's theoretically possible for you to override the input and output streams on the console to keep your own copy of the console buffer that you could read from, but it would be non-trivial (and probably couldn't support all of the edge cases such as an external program hooking into your console and reading/writing to it).
Forget about it, too much trouble, you could read from buffer and get all the current console output, but that would be too much.
My suggestion is to create a ConsoleWriter delegation, you choose how, could be a class or just an static method, and this writer would keep the last line in a property so each time you would Console.WriteLine something you just call your delegation, with your implementation and at the end it calls Console.WriteLine.
As #Servy has stated, there isn't any built-in functionality (that I know of, or can find) that can do what you want. However, there is a work-around (it's a bit of a hack, but it worked).
You can create your own buffer in memory, or on disk. Whenever you output to the Console, also output to your buffer. You can then use your buffer to read from in ways that you couldn't with the Console.
There are two ways to buffer: on disk or in memory. You can use the Console.BufferWidth and Console.BufferHeight properties to find out your buffer size. I found it simpler to do this in memory using an array of strings (each string was a line of output, and there were a number of strings in the array equal to the BufferHeight, if I remember correctly). A colleague ended up doing the same thing on disk.
You'll want to create a method to replace Console.Write and Console.WriteLine, so that you can write to both buffers at once. Something like:
public void MyWrite( string output ) {
Console.Write( output );
Array.Write( output ); // obvious pseudo-code
}
I found it helpful to wrap a Class around the array and implement methods to support it ... you could then implement your GetCharAtLocation( int i, int j ) method, as well as any other functionality you need there.
What about:
class Program {
static void Main( string[ ] args ) {
CustomizedConsole.WriteLine( "Lorem Ipsum" ); //Lorem Ipsum
Console.WriteLine( CustomizedConsole.ReadContent( 6, 5 ) ); //Ipsum
Console.WriteLine( CustomizedConsole.GetCharAtLocation( 0, 0 ) ); //L
}
}
static class CustomizedConsole {
private static List<char> buffer = new List<char>();
private static int lineCharCount = 0;
public static void Write(string s){
lineCharCount += s.Length;
buffer.AddRange( s );
Console.Write( s );
}
public static void WriteLine(string s ) {
for ( int i = 0; i < Console.BufferWidth - lineCharCount - s.Length; i++ )
s += " ";
buffer.AddRange( s );
Console.WriteLine( s );
lineCharCount = 0;
}
public static string ReadContent( int index, int count ) {
return new String(buffer.Skip( index ).Take( count ).ToArray());
}
public static char GetCharAtLocation( int x, int y ) {
return buffer[ Console.BufferHeight * x + y ];
}
}
EDIT :
As said the others this is just a trivial case where there are a lot of other things to improve. But I wrote this only as a starting point.
I am trying to do login in the phone. I am developing in c# and the library is in C++. The function "lineDevSpecific" returns the value "-2147483595", but it must to be positive.
[DllImport("Tapi32.dll", SetLastError = true)]
unsafe private static extern int lineDevSpecific(IntPtr hLine, IntPtr lpParams);
[StructLayout(LayoutKind.Sequential)]
public struct UserRec
{
//[MarshalAs(UnmanagedType.I4)]
public int dwMode=8;
public int dwParam1=201;
public int dwParam2=0;
}
unsafe static void Iniciar()
{
string vline = "Ip Office Phone: 201";
IntPtr hline = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(vline);
var sizeUserRec = Marshal.SizeOf(typeof(UserRec));
var userRec = Marshal.AllocHGlobal(sizeUserRec);
int result=lineDevSpecific(hline, userRec);
var x = (UserRec)Marshal.PtrToStructure(userRec, typeof(UserRec));
Marshal.FreeHGlobal(userRec);
}
Version 2:
I have modified the initial post adding the lineInitializeEx method and lineOpen.
These methods returns a positive value, after this lineDevSpecific continues returning the same value.
[DllImport("Tapi32.dll", CharSet = CharSet.Auto)]
unsafe private static extern long lineInitializeEx(ref uint lphLineApp, uint hInstance, uint lpfnCallback, uint lpszFriendlyAppName, ref uint lpdwNumDevs, ref uint lpdwAPIVersion, ref uint lpLineInitializeExParams);
[DllImport("Tapi32.dll", SetLastError = true)]
internal static extern long lineOpen(ref uint hLineApp, uint dwDeviceID, uint lphLine, uint dwAPIVersion, uint dwExtVersion, uint dwCallbackInstance, uint dwPrivileges, uint dwMediaModes, ref uint lpCallParams);
[DllImport("Tapi32.dll", CharSet = CharSet.Auto)]
unsafe private static extern int lineDevSpecific(uint hLine, IntPtr lpParams);
uint lineApp = 0;
uint numdevs = 0;
uint apiversion = 0;
uint exparams = 0;
uint lpcallparams = 0;
string sParams = "";
long lSize = 0;
long inicio = lineInitializeEx(ref lineApp, 0, 0, 0, ref numdevs, ref apiversion, ref exparams);
if (inicio > 0)
{
long open = lineOpen(ref lineApp, 0, 0, 0, 0, 0, 0, 0, ref lpcallparams);
//string vline = "Ip Office Phone: 201";
//IntPtr hline = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(vline);
var sizeUserRec = Marshal.SizeOf(typeof(UserRec));
var userRec = Marshal.AllocHGlobal(sizeUserRec);
int result = lineDevSpecific(lineApp, userRec);
var x = (UserRec)Marshal.PtrToStructure(userRec, typeof(UserRec));
Marshal.FreeHGlobal(userRec);
}
Solution: I have called to Julmar Atapi.
string extension = "201";
char[] c = new char[extension.Length + 2];
c[0] = (char)0x08; //login character
int i = 1;
foreach (char s in extension)
{
c[i] = s;
i++;
}
c[i] = (char)0x00; //null terminator
CurrentAddress.DeviceSpecific(Encoding.ASCII.GetBytes(c));
That is a LINEERR_ constant, see MSDN LINEERR_ Constants page
These use a hexadecimal unsigned "0x8000 00xx" style, turning them negative if you look at them as a signed int.
So this one is 0x8000 0035 LINEERR_INVALPOINTER
Your hline is wrong, this is a handle to a line not a text in a pointer. You need to get it from a lineOpen
Update for version 2
You are mixing up hLineApp and hLine. From lineOpen MSDN:
hLineApp: Handle to the application's registration with TAPI.
lphLine: Pointer to an HLINE handle that is then loaded with the handle representing the opened line device. Use this handle to identify the device when invoking other functions on the open line device.
I'm trying to use the Windows multimedia MIDI functions from C#. Specifically:
MMRESULT midiOutPrepareHeader( HMIDIOUT hmo, LPMIDIHDR lpMidiOutHdr, UINT cbMidiOutHdr );
MMRESULT midiOutUnprepareHeader( HMIDIOUT hmo, LPMIDIHDR lpMidiOutHdr, UINT cbMidiOutHdr );
MMRESULT midiStreamOut( HMIDISTRM hMidiStream, LPMIDIHDR lpMidiHdr, UINT cbMidiHdr );
MMRESULT midiStreamRestart( HMIDISTRM hms );
/* MIDI data block header */
typedef struct midihdr_tag {
LPSTR lpData; /* pointer to locked data block */
DWORD dwBufferLength; /* length of data in data block */
DWORD dwBytesRecorded; /* used for input only */
DWORD_PTR dwUser; /* for client's use */
DWORD dwFlags; /* assorted flags (see defines) */
struct midihdr_tag far *lpNext; /* reserved for driver */
DWORD_PTR reserved; /* reserved for driver */
#if (WINVER >= 0x0400)
DWORD dwOffset; /* Callback offset into buffer */
DWORD_PTR dwReserved[8]; /* Reserved for MMSYSTEM */
#endif
} MIDIHDR, *PMIDIHDR, NEAR *NPMIDIHDR, FAR *LPMIDIHDR;
From a C program, I can successfully use these functions, by doing the following:
HMIDISTRM hms;
midiStreamOpen(&hms, ...);
MIDIHDR hdr;
hdr.this = that; ...
midiStreamRestart(hms);
midiOutPrepareHeader(hms, &hdr, sizeof(MIDIHDR)); // sizeof(MIDIHDR) == 64
midiStreamOut(hms, &hdr, sizeof(MIDIHDR));
// wait for an event that is set from the midi callback when the playback has finished
WaitForSingleObject(...);
midiOutUnprepareHeader(hms, &hdr, sizeof(MIDIHDR));
The above calling sequence works and produces no errors (error checks have been omitted for readability).
For the purpose of using those in C#, I have created some P/Invoke code:
[DllImport("winmm.dll")]
public static extern int midiOutPrepareHeader(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll")]
public static extern int midiOutUnprepareHeader(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll")]
public static extern int midiStreamOut(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll")]
public static extern int midiStreamRestart(IntPtr handle);
[StructLayout(LayoutKind.Sequential)]
public struct MidiHeader
{
public IntPtr Data;
public uint BufferLength;
public uint BytesRecorded;
public IntPtr UserData;
public uint Flags;
public IntPtr Next;
public IntPtr Reserved;
public uint Offset;
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
//public IntPtr[] Reserved2;
public IntPtr Reserved0;
public IntPtr Reserved1;
public IntPtr Reserved2;
public IntPtr Reserved3;
public IntPtr Reserved4;
public IntPtr Reserved5;
public IntPtr Reserved6;
public IntPtr Reserved7;
}
The call sequence is the same as in C:
var hdr = new MidiHeader();
hdr.this = that;
midiStreamRestart(handle);
midiOutPrepareHeader(handle, ref header, headerSize); // headerSize == 64
midiStreamOut(handle, ref header, headerSize);
mre.WaitOne(); // wait until the midi playback has finished.
midiOutUnprepareHeader(handle, ref header, headerSize);
MIDI output works and the code produces no error (error checks are again omitted).
As soon as I uncomment the two lines with the array in MidiHeader, and instead remove the Reserved0 to Reserved7 fields, it doesn't work anymore. What happens is the following:
Everything is normal until and including midiStreamOut. I can hear the midi output. The playback length is correct. However, the event callback is never called when the playback ends.
At this point the value of MidiHeader.Flags is 0xe, indicating that the stream is still playing (even though the callback has been notified with the message that playback has finished). The value of MidiHeader.Flags should be 9, indicating that the stream has finished playing.
The call to midiOutUnprepareHeader fails with the error code 0x41 ("Cannot perform this operation while media data is still playing. Reset the device, or wait until the data is finished playing."). Note that resetting the device, as suggested in the error message, does in fact not fix the problem (neither does waiting or trying it multiple times).
Another variant that works correctly is if I use IntPtr instead of ref MidiHeader in the signatures of the C# declarations, and then manually allocate unmanaged memory, copying my MidiHeader structure onto that memory, and then using the allocated memory to call the functions.
Furthermore, I tried decreasing the size I'm passing to the headerSize argument to 32. Since the fields are reserved (and, in fact, didn't exist in previous version of the Windows API), they seemed to have no particular purpose anyway. However, this does not fix the problem, even though Windows should not even know that the array exists, and therefore it should not do anything. Commenting out the array entirely yet again fixes the problem (i.e., both the array as well as the 8 Reserved* fields are commented out, and the headerSize is 32).
This hints to me that the IntPtr[] Reserved2 cannot be marshaled correctly, and attempting to do so is corrupting other values. To verify that, I have created a Platform Invoke test project:
WIN32PROJECT1_API void __stdcall test_function(struct test_struct_t *s)
{
printf("%u %u %u %u %u %u %u %u\n", s->test0, s->test1, s->test2, s->test3, s->test4, s->test5, s->test6, s->test7);
for (int i = 0; i < sizeof(s->pointer_array) / sizeof(s->pointer_array[0]); ++i)
{
printf("%u ", ((uint32_t)s->pointer_array[i]) >> 16);
}
printf("\n");
}
typedef int32_t *test_ptr;
struct test_struct_t
{
test_ptr test0;
uint32_t test1;
uint32_t test2;
test_ptr test3;
uint32_t test4;
test_ptr test5;
uint32_t test6;
uint32_t test7;
test_ptr pointer_array[8];
};
Which is called from C#:
[StructLayout(LayoutKind.Sequential)]
struct TestStruct
{
public IntPtr test0;
public uint test1;
public uint test2;
public IntPtr test3;
public uint test4;
public IntPtr test5;
public uint test6;
public uint test7;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public IntPtr[] pointer_array;
}
[DllImport("Win32Project1.dll")]
static extern void test_function(ref TestStruct s);
static void Main(string[] args)
{
TestStruct s = new TestStruct();
s.test0 = IntPtr.Zero;
s.test1 = 1;
s.test2 = 2;
s.test3 = IntPtr.Add(IntPtr.Zero, 3);
s.test4 = 4;
s.test5 = IntPtr.Add(IntPtr.Zero, 5);
s.test6 = 6;
s.test7 = 7;
s.pointer_array = new IntPtr[8];
for (int i = 0; i < s.pointer_array.Length; ++i)
{
s.pointer_array[i] = IntPtr.Add(IntPtr.Zero, i << 16);
}
test_function(ref s);
Console.ReadLine();
}
And the output is as expected, hence the marshaling of the IntPtr[] pointer_array worked in this program.
I am aware that not using SafeHandle is suboptimal, however, when using that, the behavior of the MIDI functions when using the array is even weirder, so I chose to maybe tackle one problem at a time.
Why does using IntPtr[] Reserved2 cause an error?
Here is some more code to produce a complete example:
C Code
/*
* example9.c
*
* Created on: Dec 21, 2011
* Author: David J. Rager
* Email: djrager#fourthwoods.com
*
* This code is hereby released into the public domain per the Creative Commons
* Public Domain dedication.
*
* http://http://creativecommons.org/publicdomain/zero/1.0/
*/
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
HANDLE event;
static void CALLBACK example9_callback(HMIDIOUT out, UINT msg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
switch (msg)
{
case MOM_DONE:
SetEvent(event);
break;
case MOM_POSITIONCB:
case MOM_OPEN:
case MOM_CLOSE:
break;
}
}
int main()
{
unsigned int streambufsize = 24;
char* streambuf = NULL;
HMIDISTRM out;
MIDIPROPTIMEDIV prop;
MIDIHDR mhdr;
unsigned int device = 0;
streambuf = (char*)malloc(streambufsize);
if (streambuf == NULL)
goto error2;
memset(streambuf, 0, streambufsize);
if ((event = CreateEvent(0, FALSE, FALSE, 0)) == NULL)
goto error3;
memset(&mhdr, 0, sizeof(mhdr));
mhdr.lpData = streambuf;
mhdr.dwBufferLength = mhdr.dwBytesRecorded = streambufsize;
mhdr.dwFlags = 0;
// flags and event code
mhdr.lpData[8] = (char)0x90;
mhdr.lpData[9] = 63;
mhdr.lpData[10] = 0x55;
mhdr.lpData[11] = 0;
// next event
mhdr.lpData[12] = 96; // delta time?
mhdr.lpData[20] = (char)0x80;
mhdr.lpData[21] = 63;
mhdr.lpData[22] = 0x55;
mhdr.lpData[23] = 0;
if (midiStreamOpen(&out, &device, 1, (DWORD)example9_callback, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
goto error4;
//printf("sizeof midiheader = %d\n", sizeof(MIDIHDR));
if (midiOutPrepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
goto error5;
if (midiStreamRestart(out) != MMSYSERR_NOERROR)
goto error6;
if (midiStreamOut(out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
goto error7;
WaitForSingleObject(event, INFINITE);
error7:
//midiOutReset((HMIDIOUT)out);
error6:
MMRESULT blah = midiOutUnprepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR));
printf("stuff: %d\n", blah);
error5:
midiStreamClose(out);
error4:
CloseHandle(event);
error3:
free(streambuf);
error2:
//free(tracks);
error1:
//free(hdr);
return(0);
}
C# Code
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace MidiOutTest
{
class Program
{
[DllImport("winmm.dll")]
public static extern int midiStreamOpen(out IntPtr handle, ref uint deviceId, uint cMidi, MidiCallback callback, IntPtr userData, uint flags);
[DllImport("winmm.dll")]
public static extern int midiStreamOut(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll")]
public static extern int midiStreamRestart(IntPtr handle);
[DllImport("winmm.dll")]
public static extern int midiOutPrepareHeader(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll")]
public static extern int midiOutUnprepareHeader(IntPtr handle, ref MidiHeader header, uint headerSize);
[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
public static extern int midiOutGetErrorText(int mmsyserr, StringBuilder errMsg, int capacity);
[DllImport("winmm.dll")]
public static extern int midiStreamClose(IntPtr handle);
public delegate void MidiCallback(IntPtr handle, uint msg, IntPtr instance, IntPtr param1, IntPtr param2);
private static readonly ManualResetEvent mre = new ManualResetEvent(false);
private static void TestMidiCallback(IntPtr handle, uint msg, IntPtr instance, IntPtr param1, IntPtr param2)
{
Debug.WriteLine(msg.ToString());
if (msg == MOM_DONE)
{
Debug.WriteLine("MOM_DONE");
mre.Set();
}
}
public const uint MOM_DONE = 0x3C9;
public const int MMSYSERR_NOERROR = 0;
public const int MAXERRORLENGTH = 256;
public const uint CALLBACK_FUNCTION = 0x30000;
public const uint MidiHeaderSize = 64;
public static void CheckMidiOutMmsyserr(int mmsyserr)
{
if (mmsyserr != MMSYSERR_NOERROR)
{
var sb = new StringBuilder(MAXERRORLENGTH);
var errorResult = midiOutGetErrorText(mmsyserr, sb, sb.Capacity);
if (errorResult != MMSYSERR_NOERROR)
{
throw new /*Midi*/Exception("An error occurred and there was another error while attempting to retrieve the error message."/*, mmsyserr*/);
}
throw new /*Midi*/Exception(sb.ToString()/*, mmsyserr*/);
}
}
static void Main(string[] args)
{
IntPtr handle;
uint deviceId = 0;
CheckMidiOutMmsyserr(midiStreamOpen(out handle, ref deviceId, 1, TestMidiCallback, IntPtr.Zero, CALLBACK_FUNCTION));
try
{
var bytes = new byte[24];
IntPtr buffer = Marshal.AllocHGlobal(bytes.Length);
try
{
MidiHeader header = new MidiHeader();
header.Data = buffer;
header.BufferLength = 24;
header.BytesRecorded = 24;
header.UserData = IntPtr.Zero;
header.Flags = 0;
header.Next = IntPtr.Zero;
header.Reserved = IntPtr.Zero;
header.Offset = 0;
#warning uncomment if using array
//header.Reserved2 = new IntPtr[8];
// flags and event code
bytes[8] = 0x90;
bytes[9] = 63;
bytes[10] = 0x55;
bytes[11] = 0;
// next event
bytes[12] = 96;
bytes[20] = 0x80;
bytes[21] = 63;
bytes[22] = 0x55;
bytes[23] = 0;
Marshal.Copy(bytes, 0, buffer, bytes.Length);
CheckMidiOutMmsyserr(midiStreamRestart(handle));
CheckMidiOutMmsyserr(midiOutPrepareHeader(handle, ref header, MidiHeaderSize));
CheckMidiOutMmsyserr(midiStreamOut(handle, ref header, MidiHeaderSize));
mre.WaitOne();
CheckMidiOutMmsyserr(midiOutUnprepareHeader(handle, ref header, MidiHeaderSize));
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
finally
{
midiStreamClose(handle);
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MidiHeader
{
public IntPtr Data;
public uint BufferLength;
public uint BytesRecorded;
public IntPtr UserData;
public uint Flags;
public IntPtr Next;
public IntPtr Reserved;
public uint Offset;
#if false
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public IntPtr[] Reserved2;
#else
public IntPtr Reserved0;
public IntPtr Reserved1;
public IntPtr Reserved2;
public IntPtr Reserved3;
public IntPtr Reserved4;
public IntPtr Reserved5;
public IntPtr Reserved6;
public IntPtr Reserved7;
#endif
}
}
From the documentation of midiOutPrepareHeader:
Before you pass a MIDI data block to a device driver, you must prepare the buffer by passing it to the midiOutPrepareHeader function. After the header has been prepared, do not modify the buffer. After the driver is done using the buffer, call the midiOutUnprepareHeader function.
You are not adhering to this. The marshaller creates a temporary native version of your struct which lives for the duration of the call to midiOutPrepareHeader. Once midiOutPrepareHeader returns, the temporary native struct is destroyed. But the MIDI code still has a reference to it. And that's the key point, the MIDI code holds a reference to your struct and needs to be able to access it.
The version with the separately written fields works because that struct is blittable. And so the p/invoke marshaller optimises the call by pinning the managed structure which is binary compatible with the native structure. There's still a window of opportunity for the GC to relocate the struct before you call midiOutUnprepareHeader but it seems that you've not been caught out by that yet. Were you to persevere with the bittable struct you would need to pin it until you called midiOutUnprepareHeader.
So, the bottom line is that you need to provide a structure that lives until you call midiOutUnprepareHeader. Personally, I suggest that you use Marshal.AllocHGlobal, Marshal.StructureToPtr, and then Marshal.FreeHGlobal once midiOutUnprepareHeader returns. And obviously switch the parameters from ref MidiHeader to IntPtr.
I don't think I need to show you any code because it is clear from your question that you know how to do this stuff. Indeed the solution I propose is one that you've already tried and observed work. But now you know why!
I am working on WIN CE platform, developing windows forms in C#.Net.
The DeviceIoControl API is working fine with the parameters (mentioned below) in c++ console application.
PNIC_STATISTICS structure in nuiouser.h
global declarations :
TCHAR PCI1_NAME[] = _T("PCI\\ManiXX1");
TCHAR *AUB_NAME = NULL;
AUB_NAME = PCI1_NAME;
pNICStat = (PNIC_STATISTICS)malloc(sizeof(NIC_STATISTICS)) ;
pNICStat->ptcDeviceName = AUB_NAME ; //wchar_t* ptcDeviceName
DeviceIoControl( hUB94Port, //void*
IOCTL_NDISUIO_NIC_STATISTICS,
pNICStat, //PNIC_STATISTICS
0,
pNICStat, //PNIC_STATISTICS
sizeof(NIC_STATISTICS),
&dwReturnedBytes,
NULL
);
<==============================================================================>
But I'm getting problems in implementing the same with C#.Net CF for WIN-CE7
My WIN-CE Code is as follows:
Modified Structure in C#:
[StructLayout(LayoutKind.Sequential,CharSet = CharSet.Unicode)]
public struct __NIC_STAT
{
ulong Size; // Of this structure.
public Char[] ptcDeviceName; // The device name to be queried..
ulong DeviceState; // DEVICE_STATE_XXX above
ulong DeviceState; // DEVICE_STATE_XXX above
ulong MediaType; // NdisMediumXXX
ulong MediaState; // MEDIA_STATE_XXX above
ulong PhysicalMediaType;
ulong LinkSpeed; // In 100bits/s. 10Mb/s = 100000
UInt64 PacketsSent;
UInt64 PacketsReceived;
ulong InitTime; // In milliseconds
ulong ConnectTime; // In seconds
UInt64 BytesSent; // 0 - Unknown (or not supported)
UInt64 BytesReceived; // 0 - Unknown (or not supported)
UInt64 DirectedBytesReceived;
UInt64 DirectedPacketsReceived;
ulong PacketsReceiveErrors;
ulong PacketsSendErrors;
ulong ResetCount;
ulong MediaSenseConnectCount;
ulong MediaSenseDisconnectCount;
} ;
From this Structure I am just filling ptcDeviceName and trying to send.
__NIC_STAT NIC_STAT = new __NIC_STAT();
Char[] toBytes = {'P','C','I','\\','M','a','n','i','X','X','1','\0'}
NIC_STAT.ptcDeviceName = toBytes; //public Char[] ptcDeviceName; in structure
// __NIC_STAT this is the same structure as
//in nuiouser.h
int sz = Marshal.SizeOf(NIC_STAT.GetType());//sometimes Getting exception here
intptr ptr = Marshal.AllocHGlobal(sz);
Marshal.StructureToPtr((__NIC_STAT)NIC_STAT, ptr, false);
unsafe
{
DeviceIoControl(hFileHandle,
IOCTL_NDISUIO_NIC_STATISTICS,
ref ptr,
0,
ref ptr,
sz,
ref dwReturnedBytes,
0);
}//unsafe
It's corresponding prototype
[DllImport("coredll.dll", CharSet = CharSet.Auto, SetLastError = true)]
unsafe public static extern bool DeviceIoControl(
int hDevice,
int dwIoControlCode,
ref intptr InBuffer,
int nInBufferSize,
ref intptr OutBuffer,
int nOutputBufferSize,
ref int pBytesReturned,
int pOverlapped
);
In Win-CE DeviceIoControl() is getting failed, with exception and not displaying any error codes. and sometimes getting error code as 87 (INVALID PARAMETERS).
I feel ptcDeviceName is creating the problem or may be because of allocating memory for pointer (ptr) ?
In Console application we are sending ptcDeviceName as Wchar_t* but in WIN-CE so I used
public Char[] ptcDeviceName;
Can anybody tell me where I am doing wrong.?
You have a couple problems going on here.
First is that you seem to think a ulong is 32-bits in C#, but it's not. It'64 bits, so your struct is totally mapped wrong.
Second, I'm sure you need to be setting the struct Size member before passing it to the call.
Third, that ptcDeviceName member is a pointer to a wide character string. That means that in the struct itself it's 4 bytes. I'd likely make it an IntPtr. You then need to separately allocate the string, pin it, and put the pointer to it into that member slot. Since `StringToHGlobal doesn't exist in the CF, it would look something like this:
public struct __NIC_STAT
{
public uint Size;
public IntPtr ptcDeviceName;
public uint DeviceState;
public uint DeviceState;
public uint MediaType;
public uint MediaState;
public uint PhysicalMediaType;
public uint LinkSpeed;
public ulong PacketsSent;
public ulong PacketsReceived;
public uint InitTime;
public uint ConnectTime;
public ulong BytesSent;
public ulong BytesReceived;
public ulong DirectedBytesReceived;
public ulong DirectedPacketsReceived;
public uint PacketsReceiveErrors;
public uint PacketsSendErrors;
public uint ResetCount;
public uint MediaSenseConnectCount;
public uint MediaSenseDisconnectCount;
};
....
var myStruct = new __NIC_STAT();
myStruct.Size = (15 * 4) + (6 * 8);
var name = "PCI\\Manixx1\0";
var nameBytes = Encoding.Unicode.GetBytes(name);
myStruct.ptcDeviceName = Marshal.AllocHGlobal(nameBytes.Length);
try
{
Marshal.Copy(nameBytes, 0, myStruct.ptcDeviceName, nameBytes.Length);
// make the IOCTL call, a-la
NativeMethods.DeviceIoControl(...., ref myStruct, ....);
}
finally
{
Marshal.FreeHGlobal(myStruct.ptcDeviceName);
}