Illegal parameter in converting C SDK to C# - c#

I am trying to conver a C SDK to C# and am running into a "Illegal Parameter" error on the conversion of a C function.
The details of the C SDK function are listed below
#ifndef LLONG
#ifdef WIN32
#define LLONG LONG
#else //WIN64
#define LLONG INT64
#endif
#endif
#ifndef CLIENT_API
#define CLIENT_API __declspec(dllexport)
#endif
#else
#ifndef CLIENT_API
#define CLIENT_API __declspec(dllimport)
#endif
#endif
#define CALLBACK __stdcall
#define CALL_METHOD __stdcall //__cdecl
// Configuration type,corresponding to CLIENT_GetDevConfig and CLIENT_SetDevConfig
#define DH_DEV_DEVICECFG 0x0001 // Device property setup
#define DH_DEV_NETCFG 0x0002 // Network setup
#define DH_DEV_CHANNELCFG 0x0003 // Video channel setup
#define DH_DEV_PREVIEWCFG 0x0004 // Preview parameter setup
#define DH_DEV_RECORDCFG 0x0005 // Record setup
#define DH_DEV_COMMCFG 0x0006 // COM property setup
#define DH_DEV_ALARMCFG 0x0007 // Alarm property setup
#define DH_DEV_TIMECFG 0x0008 // DVR time setup
#define DH_DEV_TALKCFG 0x0009 // Audio talk parameter setup
#define DH_DEV_AUTOMTCFG 0x000A // Auto matrix setup
#define DH_DEV_VEDIO_MARTIX 0x000B // Local matrix control strategy setup
#define DH_DEV_MULTI_DDNS 0x000C // Multiple ddns setup
#define DH_DEV_SNAP_CFG 0x000D // Snapshot corresponding setup
#define DH_DEV_WEB_URL_CFG 0x000E // HTTP path setup
#define DH_DEV_FTP_PROTO_CFG 0x000F // FTP upload setup
#define DH_DEV_INTERVIDEO_CFG 0x0010 // Plaform embedded setup. Now the channel parameter represents the platform type.
// Search configuration information
CLIENT_API BOOL CALL_METHOD CLIENT_GetDevConfig(LLONG lLoginID, DWORD dwCommand, LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned,int waittime=500);
The C# info is as follows:
// [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.StdCall)]
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
// [DllImport("dhnetsdk.dll")]
public static extern bool CLIENT_GetDevConfig(long lLoginID,
uint dwCommand,
long lChannel,
IntPtr lpBuffer,
uint dwOutBufferSize,
uint lpBytesReturned,
int waittime = 500);
And I am calling the method as follows:
int t = 500;
uint BytesReturned = 0;
uint c = 8;
var lpOutBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NET_TIME)));
if (CLIENT_GetDevConfig(lLogin, c, 0, lpOutBuffer, (uint)Marshal.SizeOf(typeof(NET_TIME)), BytesReturned, t) == false)
{
Console.WriteLine("GetDevConfig FAILED");
}
[StructLayout(LayoutKind.Sequential)]
public struct NET_TIME
{
// [FieldOffset(0)]
uint dwYear;
// [FieldOffset(4)]
uint dwMonth;
// [FieldOffset(4)]
uint dwDay;
// [FieldOffset(4)]
uint dwHour;
// [FieldOffset(4)]
uint dwMinute;
// [FieldOffset(4)]
uint dwSecond;
}
I am positive the lLogin is correct since I successfully logged into the device using it.
But when I check GetLastError immediately after the call to GetDevConfig fails, it indicates a illegal parameter. So, can anybody point out the illegal parameter in the above code?
The following is my C# code with the illegal parameter issues...
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
{
static public int lLogin;
public delegate void fDisConnect(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser);
public delegate void fHaveReConnect(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool CLIENT_Init(fDisConnect cbDisConnect, uint dwUser);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void CLIENT_SetAutoReconnect(fHaveReConnect cbHaveReconnt, uint dwUser);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int CLIENT_Login(string pchDVRIP, ushort wDVRPort, string pchUserName, string pchPassword, NET_DEVICEINFO lpDeviceInfo, IntPtr error);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CLIENT_GetDevConfig(
int loginId,
uint command,
int channel,
out NET_TIME buffer,
out uint bufferSize,
IntPtr lpBytesReturned,
int waittime = 500);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool CLIENT_Logout(long lID);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void CLIENT_Cleanup();
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint CLIENT_GetLastError();
public static void fDisConnectMethod(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser)
{
System.Console.WriteLine("Disconnect");
return;
}
public static void fHaveReConnectMethod(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser)
{
System.Console.WriteLine("Reconnect success");
return;
}
[StructLayout(LayoutKind.Sequential)]
public class NET_DEVICEINFO
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)]
public byte[] sSerialNumber;
public byte byAlarmInPortNum;
public byte byAlarmOutPortNum;
public byte byDiskNum;
public byte byDVRType;
public byte byChanNum;
}
[StructLayout(LayoutKind.Sequential)]
public struct NET_TIME
{
uint dwYear;
uint dwMonth;
uint dwDay;
uint dwHour;
uint dwMinute;
uint dwSecond;
}
public static void Main()
{
fDisConnect fDisConnecthandler = fDisConnectMethod;
fHaveReConnect fHaveReConnecthandler = fHaveReConnectMethod;
NET_DEVICEINFO deviceinfo = new NET_DEVICEINFO();
IntPtr iRet = new IntPtr(0);
CLIENT_Init(fDisConnectMethod, 0);
CLIENT_SetAutoReconnect(fHaveReConnecthandler, 0);
lLogin = CLIENT_Login("192.168.1.198", 31111, "user", "password", deviceinfo, iRet);
if (lLogin <= 0)
Console.WriteLine("Login device failed");
else
{
Console.WriteLine("Login device successful");
byte[] byteout = new byte[20];
const int t = 500;
IntPtr BytesReturned;
BytesReturned = IntPtr.Zero;
const uint c = 8;
NET_TIME nt;
uint sizeofnt = (uint)Marshal.SizeOf(typeof(NET_TIME));
if (CLIENT_GetDevConfig(lLogin, c, 0, out nt, out sizeofnt, BytesReturned, t) == false)
{
uint gle = CLIENT_GetLastError();
Console.WriteLine("getDevConfig failed");
}
CLIENT_Logout(lLogin);
CLIENT_Cleanup();
}
}
}
And here is my C code that I'm trying to port to C#. It works without any issues..
#pragma comment(lib,"dhnetsdk.lib")
#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#include "dhnetsdk.h"
void CALLBACK DisConnectFunc(LONG lLoginID, char *pchDVRIP, LONG nDVRPort, DWORD dwUser)
{
printf("Disconnect.");
return;
}
void CALLBACK AutoConnectFunc(LONG lLoginID,char *pchDVRIP,LONG nDVRPort,DWORD dwUser)
{
printf("Reconnect success.");
return;
}
int main(void)
{
NET_TIME nt = {0};
NET_DEVICEINFO deviceInfo = {0};
unsigned long lLogin = 0;
LPVOID OutBuffer;
int iRet = 0;
DWORD dwRet = 0;
//Initialize the SDK, set the disconnection callback functions
CLIENT_Init(DisConnectFunc,0);
//Setting disconnection reconnection success of callback functions. If don't call this interface, the SDK will not break reconnection.
CLIENT_SetAutoReconnect(AutoConnectFunc,0);
lLogin = CLIENT_Login("192.168.1.108",31111,"user","password",&deviceInfo, &iRet);
if(lLogin <= 0)
{
printf("Login device failed");
}
else
{
OutBuffer = (LPVOID)malloc(sizeof(NET_TIME));
memset(OutBuffer, 0, sizeof(NET_TIME));
if(CLIENT_GetDevConfig( lLogin, 8 /* DH_DEV_TIMECFG */, 0, OutBuffer, sizeof(NET_TIME), &dwRet, 500) == FALSE)
{
printf("Failed\n");
}
else
{
memcpy(&nt, OutBuffer, sizeof(nt));
printf("Time %d %d %d %d %d %d\n", nt.dwYear,nt.dwMonth,nt.dwDay, nt.dwHour,nt.dwMinute, nt.dwSecond);
}
_getch();
}
CLIENT_Logout(lLogin);
CLIENT_Cleanup();
return 0;
}

Your extern function is wrongly defined. Let's take your C call example.
// Search configuration information
CLIENT_API BOOL CALL_METHOD CLIENT_GetDevConfig(LLONG lLoginID, DWORD dwCommand, LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned,int waittime=500);
As stated in comment of your post, the length of LONG is 32-bit in Win32, so you have to use an int. You can also use the keyword out to get your structure without manually use the Mashaller. I would define your function as that.
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CLIENT_GetDevConfig(int loginId, uint command, int channel, out NET_TIME buffer, out uint bufferSize, IntPtr lpBytesReturned, int waittime = 500);
Note the presence of the additional attribute MarshalAs. It indicates how the managed code should consider the return value of the pinvoke'd function.

Here are the differences that I can see:
The C++ code:
CLIENT_API BOOL CALL_METHOD CLIENT_GetDevConfig(
LLONG lLoginID,
DWORD dwCommand,
LONG lChannel,
LPVOID lpOutBuffer,
DWORD dwOutBufferSize,
LPDWORD lpBytesReturned,
int waittime
);
Now, LLONG is pointer sized, 32 bit under x86, 64 bit under x64. That translates to IntPtr for C#. I'd declare the p/invoke like this:
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CLIENT_GetDevConfig(
IntPtr lLoginID,
uint dwCommand,
int lChannel,
out NET_TIME lpOutBuffer,
uint dwOutBufferSize,
out uint lpBytesReturned,
int waittime
);
The main problems that I can see are that:
You are translated dwOutBufferSize incorrectly. It's an IN parameter but you are passing it by reference. This is the most likely explanation for the failure.
In the C++ code you pass a pointer to a DWORD variable for lpBytesReturned. In your C# code you pass IntPtr.Zero which is the null pointer.
So, I'd have the call to CLIENT_GetDevConfig looking like this:
NET_TIME nt;
uint sizeofnt = (uint)Marshal.SizeOf(typeof(NET_TIME));
uint BytesReturned;
if (!CLIENT_GetDevConfig(lLogin, 8, 0, out nt, sizeofnt, out BytesReturned, 500))
....
It may be that you can indeed pass IntPtr.Zero to the BytesReturned parameter. Perhaps it's an optional parameter. But I cannot tell that from here. However, for sure the dwOutBufferSize mis-declaration is an error.

Related

Marshaling an IntPtr[] inside a struct causes midiStream functions to bug out, but unrolling the array to a bunch of fields works

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!

WinDivert in C#

I want to call 5 WinDivert functions in a C# code, first reflex : PInvoke here are the signatures :
internal enum WINDIVERT_LAYER
{
WINDIVERT_LAYER_NETWORK = 0, /* Network layer. */
WINDIVERT_LAYER_NETWORK_FORWARD = 1 /* Network layer (forwarded packets) */
}
internal const UInt64 WINDIVERT_FLAG_SNIFF = 1;
/*
* typedef struct
{
UINT32 IfIdx;
UINT32 SubIfIdx;
UINT8 Direction;
} WINDIVERT_ADDRESS, *PWINDIVERT_ADDRESS;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_ADDRESS
{
UInt32 IfIdx;
UInt32 SubIfIdx;
byte Direction;
}
/*
* typedef struct
{
UINT8 HdrLength:4;
UINT8 Version:4;
UINT8 TOS;
UINT16 Length;
UINT16 Id;
UINT16 FragOff0;
UINT8 TTL;
UINT8 Protocol;
UINT16 Checksum;
UINT32 SrcAddr;
UINT32 DstAddr;
} WINDIVERT_IPHDR, *PWINDIVERT_IPHDR;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_IPHDR
{
byte HdrLengthAndVersion;
byte TOS;
UInt16 Length;
UInt16 Id;
UInt16 FragOff0;
byte TTL;
byte Protocol;
UInt16 Checksum;
UInt32 SrcAddr;
UInt32 DstAddr;
}
/*
* typedef struct
{
UINT16 SrcPort;
UINT16 DstPort;
UINT16 Length;
UINT16 Checksum;
} WINDIVERT_UDPHDR, *PWINDIVERT_UDPHDR;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_UDPHDR
{
UInt16 SrcPort;
UInt16 DstPort;
UInt16 Length;
UInt16 Checksum;
}
/*
* HANDLE WinDivertOpen(
__in const char *filter,
__in WINDIVERT_LAYER layer,
__in INT16 priority,
__in UINT64 flags
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertOpen", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr WinDivertOpen(
[MarshalAs(UnmanagedType.LPStr)] string filter,
WINDIVERT_LAYER layer,
Int16 priority,
UInt64 flags);
/*
* BOOL WinDivertClose(
__in HANDLE handle
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertClose", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return:MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertClose(IntPtr handle);
/*
* BOOL WinDivertRecv(
__in HANDLE handle,
__out PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *recvLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertRecv", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertRecv(IntPtr handle, out IntPtr pPacket, uint packetLen, [Optional] out IntPtr pAddr, [Optional] out uint readLen);
/*
* BOOL WinDivertHelperParsePacket(
__in PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_IPHDR *ppIpHdr,
__out_opt PWINDIVERT_IPV6HDR *ppIpv6Hdr,
__out_opt PWINDIVERT_ICMPHDR *ppIcmpHdr,
__out_opt PWINDIVERT_ICMPV6HDR *ppIcmpv6Hdr,
__out_opt PWINDIVERT_TCPHDR *ppTcpHdr,
__out_opt PWINDIVERT_UDPHDR *ppUdpHdr,
__out_opt PVOID *ppData,
__out_opt UINT *pDataLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertHelperParsePacket", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertHelperParsePacket(IntPtr pPacket, uint packetLen, [Optional] out IntPtr ppIpHdr, [Optional] out IntPtr ppIpv6Hdr,
[Optional] out IntPtr ppIcmpHdr, [Optional] out IntPtr ppTcpHdr, [Optional] out IntPtr ppUdpHdr, [Optional] out IntPtr ppData,
[Optional]out uint pDataLen);
/*
* BOOL WinDivertSend(
__in HANDLE handle,
__in PVOID pPacket,
__in UINT packetLen,
__in PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *sendLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertSend", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertSend(IntPtr handle, IntPtr pPacket, uint packetLen, IntPtr pAddr, [Optional] out uint sendLen);
I have managed to avoid (87 = ERROR_INVALID_PARAMETER), (998 = ERROR_NOACCESS) errors on calls to WinDivertOpen(), WinDivertClose() and WinDivertRecv() but I'm still getting System.AccessViolationException : Attempted to read or write protected memory. This is often an indication that other memory has been corrupted and (998 = ERROR_NOACCESS) when trying to call WinDivertHelperParsePacket().
Here's the code :
static void Main(string[] args)
{
const uint MAXBUF = 0xFFFF;
IntPtr handle;
IntPtr addr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_ADDRESS)));
IntPtr packet = Marshal.AllocHGlobal((int)MAXBUF);
uint packetLen;
IntPtr ip_header = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_IPHDR)));
IntPtr udp_header = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_UDPHDR)));
IntPtr payload;
uint payload_len;
uint sendLen;
IntPtr opt_param = IntPtr.Zero;
byte[] managedPacket;
IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
handle = NativeMethods.WinDivertOpen("udp.DstPort == 53", NativeMethods.WINDIVERT_LAYER.WINDIVERT_LAYER_NETWORK, 404, NativeMethods.WINDIVERT_FLAG_SNIFF);
if (handle == INVALID_HANDLE_VALUE) Console.WriteLine("open error:" + Marshal.GetLastWin32Error());
else
{
while (true)
{
if (!NativeMethods.WinDivertRecv(handle, out packet, MAXBUF, out addr, out packetLen))
{
Console.WriteLine("Recv error:" + Marshal.GetLastWin32Error());
continue;
}
try
{
managedPacket = new byte[(int)packetLen];
Marshal.Copy(packet, managedPacket, 0, (int)packetLen); // causes AccessViolationException
Console.WriteLine("---------------------------------");
/*for (int i = 0; i < packetLen; i++)
{
Console.Write("{0:X}", managedPacket[i]);
}*/
Console.WriteLine("---------------------------------");
}
catch(Exception ex)
{
Console.WriteLine("copy error :" + ex.Message);
}
if (!NativeMethods.WinDivertHelperParsePacket(packet, packetLen, out ip_header, out opt_param, out opt_param, out opt_param, out udp_header, out payload, out payload_len)) // causes AccessViolationException
{
Console.WriteLine("Parse error:" + Marshal.GetLastWin32Error());
//continue;
}
if (!NativeMethods.WinDivertSend(handle, packet, packetLen, addr, out sendLen))
{
Console.WriteLine("Send error:" + Marshal.GetLastWin32Error());
continue;
}
}
/*if (!NativeMethods.WinDivertClose(handle))
Console.WriteLine("close error:" + Marshal.GetLastWin32Error());*/
}
Console.ReadKey();
}
My boss told me that it's better/easier to write a COM object in C++ that wraps the C calls and expose it to C# to avoid the marshaling and memory handling pain. Should I stick to PInvoke or go the COM way ?
EDIT : Updates
I have tried two different ways of allocating unmanaged memory and both failed (unsafe code allowed) :
byte[] managedPacket = new byte[(int)packetLen];
NativeMethods.WINDIVERT_ADDRESS windivertAddr = new NativeMethods.WINDIVERT_ADDRESS();
GCHandle managedPacketHandle = GCHandle.Alloc(managedPacket, GCHandleType.Pinned);
IntPtr managedPacketPointer = managedPacketHandle.AddrOfPinnedObject();
GCHandle windivertAddrHandle = GCHandle.Alloc(windivertAddr, GCHandleType.Pinned);
IntPtr windivertAddrPointer = managedPacketHandle.AddrOfPinnedObject();
NativeMethods.WinDivertRecv(handle, out managedPacketPointer, (uint)(packetLen * Marshal.SizeOf(typeof(System.Byte))), out windivertAddrPointer , out readLen);
// output of managed array and struct fields = 0 and it still causes unhandled AccessViolationException even inside a try/catch
managedPacketHandle.Free();
windivertAddrPointer.Free();
and :
IntPtr packet = Marshal.AllocHGlobal((int)packetLen * Marshal.SizeOf(typeof(System.Byte)));
IntPtr addr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_ADDRESS)));
NativeMethods.WinDivertRecv(handle, out packet, (uint)(packetLen * Marshal.SizeOf(typeof(System.Byte))), out addr, out readLen)
byte[] managedPacket = new byte[(int)packetLen];
Marshal.Copy(packet, managedPacket, 0, (int)readLen);
NativeMethods.WINDIVERT_ADDRESS windivertAddr = (NativeMethods.WINDIVERT_ADDRESS)Marshal.PtrToStructure(addr, typeof(NativeMethods.WINDIVERT_ADDRESS));
// no output of managed array and struct fields and the same unhandled AccessViolationException
Marshal.FreeHGlobal(addr);
Marshal.FreeHGlobal(packet);
Also sometimes inside a loop, WinDivRecv fails with LastWin32Error : 6 = INVALID_HANDLE_VALUE is it because the GC is messing with the handle ? I tried GC.KeepAlive(handle) and it didn't change anything.
C++\CLI wrapper : (bridge between unmanaged C DLL and managed C# code)
[Suggested option in the comments below]
I followed these steps :
Create a C++/CLI library project
Create a Native C++ class that wraps the C functions
Create a managed C++/CLI class with a field pointing to a native class instance and wraps all the unmanaged methods and do the necessary marshalling.
Try to build ==> fails with the famous LNK2019 and LNK2028
Should I add the WinDivert DLL as a reference or only WinDivert.h ? What about the kernel driver .sys files ?
And honestly it's not easier than PInvoke but a lot worse, I still have to do the same marshalling of non-blittable data types and struct/enum definitions !

How to Call Schannel Functions from .Net/C#

I am trying to re-order/remove cipher suites due to compliance reasons (I want to use 256 bit AES and ephemeral keys) in .Net. However, using WCF TCP Transport Security, I cede all control over the security to Windows' TLS implementation and its preferred ciphers. I don't want to change the system-level ciphers.
I found this great Microsoft page that lists how to do it at the application level using BCryptEnumContextFunctions, BCryptAddContextFunction, BCryptRemoveContextFunction, but it's all C and I don't have enough P/Invoke experience to even know where to begin. I googled but didn't find anyone doing it.
The C++ code snippets on the MSDN page are below, and I need help converting them to .Net P/Invoke calls:
BCryptEnumContextFunctions
#include <stdio.h>
#include <windows.h>
#include <bcrypt.h>
void main()
{
HRESULT Status = ERROR_SUCCESS;
DWORD cbBuffer = 0;
PCRYPT_CONTEXT_FUNCTIONS pBuffer = NULL;
Status = BCryptEnumContextFunctions(
CRYPT_LOCAL,
L"SSL",
NCRYPT_SCHANNEL_INTERFACE,
&cbBuffer,
&pBuffer);
if(FAILED(Status))
{
printf_s("\n**** Error 0x%x returned by BCryptEnumContextFunctions\n", Status);
goto Cleanup;
}
if(pBuffer == NULL)
{
printf_s("\n**** Error pBuffer returned from BCryptEnumContextFunctions is null");
goto Cleanup;
}
printf_s("\n\n Listing Cipher Suites ");
for(UINT index = 0; index < pBuffer->cFunctions; ++index)
{
printf_s("\n%S", pBuffer->rgpszFunctions[index]);
}
Cleanup:
if (pBuffer != NULL)
{
BCryptFreeBuffer(pBuffer);
}
}
BCryptAddContextFunction
#include <stdio.h>
#include <windows.h>
#include <bcrypt.h>
void main()
{
SECURITY_STATUS Status = ERROR_SUCCESS;
LPWSTR wszCipher = (L"RSA_EXPORT1024_DES_CBC_SHA");
Status = BCryptAddContextFunction(
CRYPT_LOCAL,
L"SSL",
NCRYPT_SCHANNEL_INTERFACE,
wszCipher,
CRYPT_PRIORITY_TOP);
}
BCryptRemoveContextFunction
#include <stdio.h>
#include <windows.h>
#include <bcrypt.h>
void main()
{
SECURITY_STATUS Status = ERROR_SUCCESS;
LPWSTR wszCipher = (L"TLS_RSA_WITH_RC4_128_SHA");
Status = BCryptRemoveContextFunction(
CRYPT_LOCAL,
L"SSL",
NCRYPT_SCHANNEL_INTERFACE,
wszCipher);
}
Could someone please help me convert these to .Net so I can call them from managed code to adjust the ciphers? Thanks!
Edit:
Later last night, I tried the following in a test program (still have no idea what I'm doing in P/Invoke):
// I found this in bcrypt.h
const uint CRYPT_LOCAL = 0x00000001;
// I can't find this anywhere in Microsoft's headers in my SDK,
// but I found some random .c file with it in there. No idea
// what this constant actually is according to Microsoft
const uint NCRYPT_SCHANNEL_INTERFACE = 0x00010002;
public static void DoStuff()
{
PCRYPT_CONTEXT_FUNCTIONS pBuffer = new PCRYPT_CONTEXT_FUNCTIONS();
pBuffer.rgpszFunctions = String.Empty.PadRight(1500);
uint cbBuffer = (uint)Marshal.SizeOf(typeof(PCRYPT_CONTEXT_FUNCTIONS));
uint Status = BCryptEnumContextFunctions(
CRYPT_LOCAL,
"SSL",
NCRYPT_SCHANNEL_INTERFACE,
ref cbBuffer,
ref pBuffer);
Console.WriteLine(Status);
Console.WriteLine(pBuffer);
Console.WriteLine(cbBuffer);
Console.WriteLine(pBuffer.cFunctions);
Console.WriteLine(pBuffer.rgpszFunctions);
}
/*
typedef struct _CRYPT_CONTEXT_FUNCTIONS {
ULONG cFunctions;
PWSTR rgpszFunctions;
} CRYPT_CONTEXT_FUNCTIONS, *PCRYPT_CONTEXT_FUNCTIONS;
*/
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct PCRYPT_CONTEXT_FUNCTIONS
{
public uint cFunctions;
public string rgpszFunctions;
}
/*
NTSTATUS WINAPI BCryptEnumContextFunctions(
ULONG dwTable,
LPCWSTR pszContext,
ULONG dwInterface,
ULONG *pcbBuffer,
PCRYPT_CONTEXT_FUNCTIONS *ppBuffer
);
*/
[DllImport("Bcrypt.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern uint BCryptEnumContextFunctions(uint dwTable, string pszContext, uint dwInterface, ref uint pcbBuffer, ref PCRYPT_CONTEXT_FUNCTIONS ppBuffer);
The output right now of the only method I got even this far with is:
0
MyClass+PCRYPT_CONTEXT_FUNCTIONS
2400
8934576
[1500 spaces that I initialized rgpszFunctions with, not the cipher functions]
This is a poorly documented library. For instance, the declaration of CRYPT_CONTEXT_FUNCTION_PROVIDERS is in fact:
typedef struct _CRYPT_CONTEXT_FUNCTION_PROVIDERS
{
ULONG cProviders;
PWSTR *rgpszProviders;
}
CRYPT_CONTEXT_FUNCTION_PROVIDERS, *PCRYPT_CONTEXT_FUNCTION_PROVIDERS;
This is lifted directly from bcrypt.h.
Anyway, here's a translation of the C++ code for you:
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)]
static extern uint BCryptEnumContextFunctions(uint dwTable, string pszContext, uint dwInterface, ref uint pcbBuffer, ref IntPtr ppBuffer);
[DllImport("Bcrypt.dll")]
static extern void BCryptFreeBuffer(IntPtr pvBuffer);
[DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)]
static extern uint BCryptAddContextFunction(uint dwTable, string pszContext, uint dwInterface, string pszFunction, uint dwPosition);
[DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)]
static extern uint BCryptRemoveContextFunction(uint dwTable, string pszContext, uint dwInterface, string pszFunction);
[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_CONTEXT_FUNCTIONS
{
public uint cFunctions;
public IntPtr rgpszFunctions;
}
const uint CRYPT_LOCAL = 0x00000001;
const uint NCRYPT_SCHANNEL_INTERFACE = 0x00010002;
const uint CRYPT_PRIORITY_TOP = 0x00000000;
const uint CRYPT_PRIORITY_BOTTOM = 0xFFFFFFFF;
public static void DoStuff()
{
uint cbBuffer = 0;
IntPtr ppBuffer = IntPtr.Zero;
uint Status = BCryptEnumContextFunctions(
CRYPT_LOCAL,
"SSL",
NCRYPT_SCHANNEL_INTERFACE,
ref cbBuffer,
ref ppBuffer);
if (Status == 0)
{
CRYPT_CONTEXT_FUNCTIONS functions = (CRYPT_CONTEXT_FUNCTIONS)Marshal.PtrToStructure(ppBuffer, typeof(CRYPT_CONTEXT_FUNCTIONS));
Console.WriteLine(functions.cFunctions);
IntPtr pStr = functions.rgpszFunctions;
for (int i = 0; i < functions.cFunctions; i++)
{
Console.WriteLine(Marshal.PtrToStringUni(Marshal.ReadIntPtr(pStr)));
pStr += IntPtr.Size;
}
BCryptFreeBuffer(ppBuffer);
}
}
static void Main(string[] args)
{
DoStuff();
Console.ReadLine();
}
}
}
On my machine the output is:
30
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_RC4_128_SHA
TLS_RSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA_P256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA_P384
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256_P256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256_P256
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384_P384
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384_P384
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA_P256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA_P384
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA_P256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA_P384
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
TLS_DHE_DSS_WITH_AES_128_CBC_SHA
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
TLS_DHE_DSS_WITH_AES_256_CBC_SHA
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
TLS_RSA_WITH_RC4_128_MD5
SSL_CK_RC4_128_WITH_MD5
SSL_CK_DES_192_EDE3_CBC_WITH_MD5
TLS_RSA_WITH_NULL_SHA256
TLS_RSA_WITH_NULL_SHA
I urge you to get to grips with some simple C++ to make progress. Take the example from MSDN and compile it. Run it under the debugger and get to know it. Use Visual Studio to locate definitions and declarations. For instance, Visual Studio took me straight to the definition of NCRYPT_SCHANNEL_INTERFACE.

How to port enum and struct to C#?

For learning purposes, I want to port some C code to C#. I already ported some code, but this one gives me major headaches. How do I convert this code to C#?
typedef enum
{
PXA_I2C_FAST_SPEED = 1,
PXA_I2C_NORMAL_SPEED
} PXA_I2C_SPEED_T;
typedef enum
{
I2C_OPCODE_READ = 1,
I2C_OPCODE_WRITE = 2,
I2C_OPCODE_READ_ONESTOP = 3,
I2C_OPCODE_WRITE_ONESTOP = 4,
} PXA_I2C_OPERATION_CODE;
typedef struct
{
/* input: */
DWORD mTransactions;
PXA_I2C_SPEED_T mClkSpeed;
UCHAR mDeviceAddr; //7 bit
PXA_I2C_OPERATION_CODE *mOpCode;
DWORD *mBufferOffset;
DWORD *mTransLen;
/* output: */
//DWORD mErrorCode;
UCHAR *mBuffer;
} PXA_I2CTRANS_T;
Then somewhere later the struct PXA_I2CTRANS_T is used with DeviceIoControl like this:
DeviceIoControl (hDevice, IOCTL_I2C_TRANSACT, NULL, 0, pTrans, sizeof(PXA_I2CTRANS_T), &BytesWritten, NULL);
Do I have to use IntPtr?
Do I have to shift the ClockSpeed into the DeviceAddress (because DeviceAddress is supposed to be 7 bits, but in C# it's 8 bits)?
And why is the struct used like an output buffer, when it is clearly something to send (with reserved memory to store the output, too)?
All I have now is this:
[DllImport("coredll.dll", SetLastError = true)]
private static extern bool DeviceIoControl(
IntPtr hDevice,
uint dwIoControlCode,
IntPtr InBuffer,
uint nInBufferSize,
[In, Out] IntPtr OutBuffer,
uint nOutBufferSize,
IntPtr pBytesReturned,
IntPtr lpOverlapped);
public enum Speed
{
Fast = 1,
Slow
}
public enum OperationCode
{
Read = 1,
Write = 2,
ReadOneStop = 3,
WriteOneStop = 4
}
[StructLayout(LayoutKind.Sequential)]
public struct TransactionData
{
public uint Transactions;
public I2cDevice.Speed ClockSpeed;
public byte DeviceAddress;
public IntPtr OpCode;
public IntPtr BufferOffset;
public IntPtr TransactionLength;
public IntPtr Buffer;
}
Later I pinned byte arrays to the structure and marshalled it, so I can do this:
TransactionData data = new TransactionData();
//declaring some arrays, allocating memory and pinning them to the struct
//also filling the non pointer fields with data
GCHandle handle1 = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr p = handle1.AddrOfPinnedObject();
DeviceIoControl(Handle, CtrlCode.Transact, IntPtr.Zero, 0, p, (uint)Marshal.SizeOf(typeof(TransactionData)), bytesreturned, IntPtr.Zero);

Find certificate by hash in Store C# using CryptoAPI

I would like to get certificate from Store using CryptoAPI P/Invoke. But I encountered some problems.
I can open store, but not find certificate. I can not understande why. The same code works on C++.
I would like to use CryptoAPI, because .NET only enable to use key of certificates with key exportable marked "yes"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
namespace capp
{
public class Crypto
{
#region CONSTS
// #define CERT_COMPARE_SHIFT 16
public const Int32 CERT_COMPARE_SHIFT = 16;
// #define CERT_STORE_PROV_SYSTEM_W ((LPCSTR) 10)
public const Int32 CERT_STORE_PROV_SYSTEM_W = 10;
// #define CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W
public const Int32 CERT_STORE_PROV_SYSTEM = CERT_STORE_PROV_SYSTEM_W;
// #define CERT_SYSTEM_STORE_CURRENT_USER_ID 1
public const Int32 CERT_SYSTEM_STORE_CURRENT_USER_ID = 1;
// #define CERT_SYSTEM_STORE_LOCATION_SHIFT 16
public const Int32 CERT_SYSTEM_STORE_LOCATION_SHIFT = 16;
// #define CERT_SYSTEM_STORE_CURRENT_USER \
// (CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT)
public const Int32 CERT_SYSTEM_STORE_CURRENT_USER =
CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT;
// #define CERT_COMPARE_SHA1_HASH 1
public const Int32 CERT_COMPARE_SHA1_HASH = 1;
// #define CERT_FIND_SHA1_HASH (CERT_COMPARE_SHA1_HASH << CERT_COMPARE_SHIFT)
public const Int32 CERT_FIND_SHA1_HASH = (CERT_COMPARE_SHA1_HASH << CERT_COMPARE_SHIFT);
// #define X509_ASN_ENCODING 0x00000001
public const Int32 X509_ASN_ENCODING = 0x00000001;
// #define PKCS_7_ASN_ENCODING 0x00010000
public const Int32 PKCS_7_ASN_ENCODING = 0x00010000;
// #define MY_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
public const Int32 MY_ENCODING_TYPE = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
#endregion
#region STRUCTS
// typedef struct _CRYPTOAPI_BLOB
// {
// DWORD cbData;
// BYTE *pbData;
// } CRYPT_HASH_BLOB, CRYPT_INTEGER_BLOB,
// CRYPT_OBJID_BLOB, CERT_NAME_BLOB;
[StructLayout(LayoutKind.Sequential)]
public struct CRYPTOAPI_BLOB
{
public Int32 cbData;
public Byte[] pbData;
}
#endregion
#region FUNCTIONS (IMPORTS)
// HCERTSTORE WINAPI CertOpenStore(
// LPCSTR lpszStoreProvider,
// DWORD dwMsgAndCertEncodingType,
// HCRYPTPROV hCryptProv,
// DWORD dwFlags,
// const void* pvPara
// );
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertOpenStore(
Int32 lpszStoreProvider,
Int32 dwMsgAndCertEncodingType,
IntPtr hCryptProv,
Int32 dwFlags,
String pvPara
);
// BOOL WINAPI CertCloseStore(
// HCERTSTORE hCertStore,
// DWORD dwFlags
// );
[DllImport("Crypt32.dll", SetLastError = true)]
public static extern Boolean CertCloseStore(
IntPtr hCertStore,
Int32 dwFlags
);
// PCCERT_CONTEXT WINAPI CertFindCertificateInStore(
// HCERTSTORE hCertStore,
// DWORD dwCertEncodingType,
// DWORD dwFindFlags,
// DWORD dwFindType,
// const void* pvFindPara,
// PCCERT_CONTEXT pPrevCertContext
// );
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertFindCertificateInStore(
IntPtr hCertStore,
Int32 dwCertEncodingType,
Int32 dwFindFlags,
Int32 dwFindType,
//String pvFindPara,
ref CRYPTOAPI_BLOB pvFindPara,
IntPtr pPrevCertContext
);
// BOOL WINAPI CertFreeCertificateContext(
// PCCERT_CONTEXT pCertContext
// );
[DllImport("Crypt32.dll", SetLastError = true)]
public static extern Boolean CertFreeCertificateContext(
IntPtr pCertContext
);
#endregion
}
class Program
{
const string MY = "MY";
static void Main(string[] args)
{
IntPtr hCertCntxt = IntPtr.Zero;
IntPtr hStore = IntPtr.Zero;
hStore = Crypto.CertOpenStore(Crypto.CERT_STORE_PROV_SYSTEM,
Crypto.MY_ENCODING_TYPE,
IntPtr.Zero,
Crypto.CERT_SYSTEM_STORE_CURRENT_USER,
MY);
Console.WriteLine("Store Handle:\t0x{0:X}", hStore.ToInt32());
String sha1Hex = "7a0b021806bffdb826205dac094030f8045d4daa";
// Convert to bin
int tam = sha1Hex.Length / 2;
byte[] sha1Bin = new byte[tam];
int aux = 0;
for (int i = 0; i < tam; ++i)
{
String str = sha1Hex.Substring(aux, 2);
sha1Bin[i] = (byte)Convert.ToInt32(str, 16);
aux = aux + 2;
}
Crypto.CRYPTOAPI_BLOB cryptBlob;
cryptBlob.cbData = sha1Bin.Length;
cryptBlob.pbData = sha1Bin;
if (hStore != IntPtr.Zero)
{
Console.WriteLine("Inside Store");
hCertCntxt = Crypto.CertFindCertificateInStore(
hStore,
Crypto.MY_ENCODING_TYPE,
0,
Crypto.CERT_FIND_SHA1_HASH,
ref cryptBlob,
IntPtr.Zero);
if (hCertCntxt != IntPtr.Zero)
Console.WriteLine("Certificate found!");
else
Console.WriteLine("Could not find ");
}
if (hCertCntxt != IntPtr.Zero)
Crypto.CertFreeCertificateContext(hCertCntxt);
if (hStore != IntPtr.Zero)
Crypto.CertCloseStore(hStore, 0);
}
}
}
Reference link to map CrytpoAPI to C# http://blogs.msdn.com/b/alejacma/archive/2007/11/23/p-invoking-cryptoapi-in-net-c-version.aspx
You can't just redefine CertFindCertificateInStore to take a ref CRYPTOAPI_BLOB.
There might be an easier way, but if you use these definitions:
[StructLayout(LayoutKind.Sequential)]
public struct CRYPTOAPI_BLOB
{
public Int32 cbData;
public IntPtr pbData;
}
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertFindCertificateInStore(
IntPtr hCertStore,
Int32 dwCertEncodingType,
Int32 dwFindFlags,
Int32 dwFindType,
IntPtr pvFindPara,
IntPtr pPrevCertContext
);
and call them like this:
Crypto.CRYPTOAPI_BLOB cryptBlob;
cryptBlob.cbData = sha1Bin.Length;
GCHandle h1 = default(GCHandle);
GCHandle h2 = default(GCHandle);
try{
h1 = GCHandle.Alloc(sha1Bin, GCHandleType.Pinned);
cryptBlob.pbData = h1.AddrOfPinnedObject();
h2 = GCHandle.Alloc(cryptBlob, GCHandleType.Pinned);
hCertCntxt = Crypto.CertFindCertificateInStore(
hStore,
Crypto.MY_ENCODING_TYPE,
0,
Crypto.CERT_FIND_SHA1_HASH,
h2.AddrOfPinnedObject(),
IntPtr.Zero);
}
finally{
if(h1!=default(GCHandle)) h1.Free();
if(h2!=default(GCHandle)) h2.Free();
}
, it should work.
If you are using .Net then why not use "System.Security.Cryptography" to do this?
Example:
X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 cert = store.Certificates
.Find(X509FindType.FindByThumbprint, thumbprint, false)
.OfType<X509Certificate2>()
.FirstOrDefault();

Categories