C# - compare two SecureStrings for equality - c#

I have a WPF application with two PasswordBoxes, one for the password and another for the password to be entered a second time for confirmation purposes. I was wanting to use PasswordBox.SecurePassword to get the SecureString of the password, but I need to be able to compare the contents of the two PasswordBoxes to ensure equality before I accept the password. However, two identical SecureStrings are not considered equal:
var secString1 = new SecureString();
var secString2 = new SecureString();
foreach (char c in "testing")
{
secString1.AppendChar(c);
secString2.AppendChar(c);
}
Assert.AreEqual(secString1, secString2); // This fails
I was thinking comparing the Password property of the PasswordBoxes would defeat the point of accessing only SecurePassword because I'd be reading the plain-text password. What should I do to compare the two passwords without sacrificing security?
Edit: based on this question, I'm checking out this blog post about "using the Marshal class to convert the SecureString to ANSI or Unicode or a BSTR", then maybe I can compare those.

This doesn't have unsafe blocks and won't display the password in plaintext:
public static bool IsEqualTo(this SecureString ss1, SecureString ss2)
{
IntPtr bstr1 = IntPtr.Zero;
IntPtr bstr2 = IntPtr.Zero;
try
{
bstr1 = Marshal.SecureStringToBSTR(ss1);
bstr2 = Marshal.SecureStringToBSTR(ss2);
int length1 = Marshal.ReadInt32(bstr1, -4);
int length2 = Marshal.ReadInt32(bstr2, -4);
if (length1 == length2)
{
for (int x = 0; x < length1; ++x)
{
byte b1 = Marshal.ReadByte(bstr1, x);
byte b2 = Marshal.ReadByte(bstr2, x);
if (b1 != b2) return false;
}
}
else return false;
return true;
}
finally
{
if (bstr2 != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstr2);
if (bstr1 != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstr1);
}
}
Edit: Fixed the leak as recommended by Alex J.

It looks like you could use this to compare the two SecureStrings.
It uses unsafe code to iterate through the strings:
bool SecureStringEqual(SecureString s1, SecureString s2)
{
if (s1 == null)
{
throw new ArgumentNullException("s1");
}
if (s2 == null)
{
throw new ArgumentNullException("s2");
}
if (s1.Length != s2.Length)
{
return false;
}
IntPtr bstr1 = IntPtr.Zero;
IntPtr bstr2 = IntPtr.Zero;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
bstr1 = Marshal.SecureStringToBSTR(s1);
bstr2 = Marshal.SecureStringToBSTR(s2);
unsafe
{
for (Char* ptr1 = (Char*)bstr1.ToPointer(), ptr2 = (Char*)bstr2.ToPointer();
*ptr1 != 0 && *ptr2 != 0;
++ptr1, ++ptr2)
{
if (*ptr1 != *ptr2)
{
return false;
}
}
}
return true;
}
finally
{
if (bstr1 != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(bstr1);
}
if (bstr2 != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(bstr2);
}
}
}
I have modified it below to work without unsafe code (note however you are able to see the string in plain text when debugging):
Boolean SecureStringEqual(SecureString secureString1, SecureString secureString2)
{
if (secureString1 == null)
{
throw new ArgumentNullException("s1");
}
if (secureString2 == null)
{
throw new ArgumentNullException("s2");
}
if (secureString1.Length != secureString2.Length)
{
return false;
}
IntPtr ss_bstr1_ptr = IntPtr.Zero;
IntPtr ss_bstr2_ptr = IntPtr.Zero;
try
{
ss_bstr1_ptr = Marshal.SecureStringToBSTR(secureString1);
ss_bstr2_ptr = Marshal.SecureStringToBSTR(secureString2);
String str1 = Marshal.PtrToStringBSTR(ss_bstr1_ptr);
String str2 = Marshal.PtrToStringBSTR(ss_bstr2_ptr);
return str1.Equals(str2);
}
finally
{
if (ss_bstr1_ptr != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(ss_bstr1_ptr);
}
if (ss_bstr2_ptr != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(ss_bstr2_ptr);
}
}
}

Translating #NikolaNovák answer to plain PowerShell:
param(
[Parameter(mandatory=$true,position=0)][SecureString]$ss1,
[Parameter(mandatory=$true,position=1)][SecureString]$ss2
)
function IsEqualTo{
param(
[Parameter(mandatory=$true,position=0)][SecureString]$ss1,
[Parameter(mandatory=$true,position=1)][SecureString]$ss2
)
begin{
[IntPtr] $bstr1 = [IntPtr]::Zero;
[IntPtr] $bstr2 = [IntPtr]::Zero;
[bool]$answer=$true;
}
process{
try{
$bstr1 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ss1);
$bstr2 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ss2);
[int]$length1 = [System.Runtime.InteropServices.Marshal]::ReadInt32($bstr1, -4);
[int]$length2 = [System.Runtime.InteropServices.Marshal]::ReadInt32($bstr2, -4);
if ($length1 -eq $length2){
for ([int]$x -eq 0; $x -lt $length1; ++$x){
[byte]$b1 = [System.Runtime.InteropServices.Marshal]::ReadByte($bstr1, $x);
[byte]$b2 = [System.Runtime.InteropServices.Marshal]::ReadByte($bstr2, $x);
if ($b1 -ne $b2){
$answer=$false;
}
}
}
else{ $answer=$false;}
}
catch{
}
finally
{
if ($bstr2 -ne [IntPtr]::Zero){ [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2)};
if ($bstr1 -ne [IntPtr]::Zero){ [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1)};
}
}
END{
return $answer
}
}
IsEqualTo -ss1 $ss1 -ss2 $ss2

If the code is running on Windows Vista or higher, here is a version that's based on the CompareStringOrdinal Windows function, so there's no plain text, all buffers stay unmanaged. Bonus is it supports case-insensitive comparison.
public static bool EqualsOrdinal(this SecureString text1, SecureString text2, bool ignoreCase = false)
{
if (text1 == text2)
return true;
if (text1 == null)
return text2 == null;
if (text2 == null)
return false;
if (text1.Length != text2.Length)
return false;
var b1 = IntPtr.Zero;
var b2 = IntPtr.Zero;
try
{
b1 = Marshal.SecureStringToBSTR(text1);
b2 = Marshal.SecureStringToBSTR(text2);
return CompareStringOrdinal(b1, text1.Length, b2, text2.Length, ignoreCase) == CSTR_EQUAL;
}
finally
{
if (b1 != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(b1);
}
if (b2 != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(b2);
}
}
}
public static bool EqualsOrdinal(this SecureString text1, string text2, bool ignoreCase = false)
{
if (text1 == null)
return text2 == null;
if (text2 == null)
return false;
if (text1.Length != text2.Length)
return false;
var b = IntPtr.Zero;
try
{
b = Marshal.SecureStringToBSTR(text1);
return CompareStringOrdinal(b, text1.Length, text2, text2.Length, ignoreCase) == CSTR_EQUAL;
}
finally
{
if (b != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(b);
}
}
}
private const int CSTR_EQUAL = 2;
[DllImport("kernel32")]
private static extern int CompareStringOrdinal(IntPtr lpString1, int cchCount1, IntPtr lpString2, int cchCount2, bool bIgnoreCase);
[DllImport("kernel32")]
private static extern int CompareStringOrdinal(IntPtr lpString1, int cchCount1, [MarshalAs(UnmanagedType.LPWStr)] string lpString2, int cchCount2, bool bIgnoreCase);

You could take a different approach. I hit the same problem in my code, the comparison of a password and a confirmation, both of type SecureString. I realised that the end goal was that the new password needs to be stored in the database as a base-64 string. So what I did was simply pass the confirmation string through the same code as if I were going to write it to the database. Then, when I have two base-64 strings, I compare them at that point, which is a simple string comparison.
It does take a bit more plumbing to communicate any failure all the way back to the UI layer, but the end result seemed acceptable. This code hopefully is enough to give the basic idea.
private string CalculateHash( SecureString securePasswordString, string saltString )
{
IntPtr unmanagedString = IntPtr.Zero;
try
{
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode( securePasswordString );
byte[] passwordBytes = Encoding.UTF8.GetBytes( Marshal.PtrToStringUni( unmanagedString ) );
byte[] saltBytes = Encoding.UTF8.GetBytes( saltString );
byte[] passwordPlusSaltBytes = new byte[ passwordBytes.Length + saltBytes.Length ];
Buffer.BlockCopy( passwordBytes, 0, passwordPlusSaltBytes, 0, passwordBytes.Length );
Buffer.BlockCopy( saltBytes, 0, passwordPlusSaltBytes, passwordBytes.Length, saltBytes.Length );
HashAlgorithm algorithm = new SHA256Managed();
return Convert.ToBase64String( algorithm.ComputeHash( passwordPlusSaltBytes ) );
}
finally
{
if( unmanagedString != IntPtr.Zero )
Marshal.ZeroFreeGlobalAllocUnicode( unmanagedString );
}
}
string passwordSalt = "INSERT YOUR CHOSEN METHOD FOR CONSTRUCTING A PASSWORD SALT HERE";
string passwordHashed = CalculateHash( securePasswordString, passwordSalt );
string confirmPasswordHashed = CalculateHash( secureConfirmPasswordString, passwordSalt );
if( passwordHashed == confirmPasswordHashed )
{
// Both matched so go ahead and persist the new password.
}
else
{
// Strings don't match, so communicate the failure back to the UI.
}
I am a bit of a newbie at security programming, so I welcome any suggestions for improvement.

Related

How to get port a process is listening on in .net?

As several blogposts and Stackoverflow answers suggest, it is trivial to get this information via a combination of get-process and Get-NetTCPConnection commandlets. It is possible to execute these commands via .net code, parse the output and retrieve the information.
Is it not possible to get the port number a process is listening on in pure .net code using just the .net libraries? System.Diagnostics.Process.GetProcesByName returns a ton of information via the Process object, except for the port the process is listening on.
Any leads highly appreciated.
Unfortunately, IPGlobalProperties.GetIPGlobalProperties() does not return any information on which process is holding the socket, as it uses GetTcpTable not GetTcpTable2.
You would need to code it yourself. The below code works for TCP over IPv4. You would need similar code for UDP and IPv6.
[DllImport("Iphlpapi.dll", ExactSpelling = true)]
static extern int GetTcpTable2(
IntPtr TcpTable,
ref int SizePointer,
bool Order
);
[StructLayout(LayoutKind.Sequential)]
struct MIB_TCPTABLE
{
public int dwNumEntries;
}
[StructLayout(LayoutKind.Sequential)]
struct MIB_TCPROW2
{
public MIB_TCP_STATE dwState;
public int dwLocalAddr;
public byte localPort1;
public byte localPort2;
// Ports are only 16 bit values (in network WORD order, 3,4,1,2).
// There are reports where the high order bytes have garbage in them.
public byte ignoreLocalPort3;
public byte ignoreLocalPort4;
public int dwRemoteAddr;
public byte remotePort1;
public byte remotePort2;
// Ports are only 16 bit values (in network WORD order, 3,4,1,2).
// There are reports where the high order bytes have garbage in them.
public byte ignoreremotePort3;
public byte ignoreremotePort4;
public int dwOwningPid;
public TCP_CONNECTION_OFFLOAD_STATE dwOffloadState;
}
public enum MIB_TCP_STATE
{
Closed = 1,
Listen,
SynSent,
SynRcvd,
Established,
FinWait1,
FinWait2,
CloseWait,
Closing,
LastAck,
TimeWait,
DeleteTcb
}
enum TCP_CONNECTION_OFFLOAD_STATE
{
TcpConnectionOffloadStateInHost,
TcpConnectionOffloadStateOffloading,
TcpConnectionOffloadStateOffloaded,
TcpConnectionOffloadStateUploading,
TcpConnectionOffloadStateMax
}
static List<IPEndPoint> GetSocketsForProcess(int pid, MIB_TCP_STATE state = MIB_TCP_STATE.Established)
{
const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
var size = 0;
var result = GetTcpTable2(IntPtr.Zero, ref size, false);
if (result != ERROR_INSUFFICIENT_BUFFER)
throw new Win32Exception(result);
var ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(size);
result = GetTcpTable2(ptr, ref size, false);
if (result != 0)
throw new Win32Exception(result);
var list = new List<IPEndPoint>();
var count = Marshal.ReadInt32(ptr);
var curPtr = ptr + Marshal.SizeOf<MIB_TCPTABLE>();
var length = Marshal.SizeOf<MIB_TCPROW2>();
for(var i = 0; i < count; i++)
{
var row = Marshal.PtrToStructure<MIB_TCPROW2>(curPtr);
if(row.dwOwningPid == pid && row.dwState == state)
list.Add(new IPEndPoint(row.dwLocalAddr, row.localPort1 << 8 | row.localPort2));
curPtr += length;
}
return list;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
I had similar task not long ago. Here is complete .NET 6 code that you should be able to adopt for your particular needs:
public static int CheckConnection(string[] servers)
{
try
{
var srvs = servers.ToDictionary(k => k.Split("/", StringSplitOptions.RemoveEmptyEntries)[1], v => false);
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
IPEndPoint[] endPoints = ipProperties.GetActiveTcpListeners();
TcpConnectionInformation[] tcpConnections = ipProperties.GetActiveTcpConnections();
var count = 0;
foreach (var info in tcpConnections)
{
var ep = string.Format("{0}:{1}", info.RemoteEndPoint.Address, info.RemoteEndPoint.Port);
if (info.State.ToString().ToUpper() != "ESTABLISHED")
continue;
if (srvs.ContainsKey(ep))
{
count++;
srvs[ep] = true;
}
}
var sb = new StringBuilder();
foreach (var kvp in srvs)
{
sb.AppendFormat("{0,-21}: {1}", kvp.Key, kvp.Value ? "OK" : "FAIL");
sb.AppendLine();
}
Program.logger.Trace($"ZMQF. Connection check:\n{sb}");
return count;
}
catch (Exception ex)
{
Program.logger.Fatal(ex, $"ZMQF. CheckConnection exception. Servers: {(string.Join(",", servers))}");
return -1;
}
}

Is there a way to use CredUIPromptForWindowsCredentialsW without unpacking the buffer (C++/CLR)?

A software I am writing is about to take an action that requires the current logged in user is actually the one taking the action. So I want to have windows just ask for the current user's password or biometrics or whatever before the action is allowed to continue.
I used an interop for UserConsentVerifier from another post (Code Below).
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Security.Credentials.UI;
namespace UWPInterop
{
//MIDL_INTERFACE("39E050C3-4E74-441A-8DC0-B81104DF949C")
//IUserConsentVerifierInterop : public IInspectable
//{
//public:
// virtual HRESULT STDMETHODCALLTYPE RequestVerificationForWindowAsync(
// /* [in] */ HWND appWindow,
// /* [in] */ HSTRING message,
// /* [in] */ REFIID riid,
// /* [iid_is][retval][out] */ void** asyncOperation) = 0;
//};
[System.Runtime.InteropServices.Guid("39E050C3-4E74-441A-8DC0-B81104DF949C")]
[System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIInspectable)]
public interface IUserConsentVerifierInterop
{
IAsyncOperation<UserConsentVerificationResult> RequestVerificationForWindowAsync(IntPtr appWindow, [MarshalAs(UnmanagedType.HString)] string Message, [In] ref Guid riid);
}
//Helper to initialize UserConsentVerifier
public static class UserConsentVerifierInterop
{
public static IAsyncOperation<UserConsentVerificationResult> RequestVerificationForWindowAsync(IntPtr hWnd, string Message)
{
IUserConsentVerifierInterop userConsentVerifierInterop = (IUserConsentVerifierInterop)WindowsRuntimeMarshal.GetActivationFactory(typeof(UserConsentVerifier));
Guid guid = typeof(IAsyncOperation<UserConsentVerificationResult>).GUID;
return userConsentVerifierInterop.RequestVerificationForWindowAsync(hWnd, Message, ref guid);
}
}
}
This works fine if a Windows Hello is setup. It returns DeviceNotPresent when its not or similar errors. Trying to find an alternative where the windows password is provided instead. This code works but I'm not entirely happy with the fact I am using a password in the app's memory. (C++/CLR)
bool ValidateUser(String ^caption, String ^message)
{
bool result = false;
String^ userName = WindowsIdentity::GetCurrent()->Name;
std::wstring strUsername = marshal_as<std::wstring>(userName);
std::wstring strCaption = marshal_as<std::wstring>(caption);
std::wstring strMessage = marshal_as<std::wstring>(message);
CREDUI_INFOW info;
ZeroMemory(&info, sizeof(info));
info.cbSize = sizeof(info);
info.pszMessageText = strMessage.c_str();
info.pszCaptionText = strCaption.c_str();
ULONG authPackage = 0;
LPVOID pOut;
ULONG bufSize;
DWORD inBuffer = 0;
std::vector<uint8_t> credBuffer;
if (!CredPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS, (LPWSTR)strUsername.c_str(), L"", NULL, &inBuffer)
&& ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
{
credBuffer.resize(inBuffer);
if (!CredPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS, (LPWSTR)strUsername.c_str(), L"", credBuffer.data(), &inBuffer))
{
return false;
}
}
DWORD dwResult = CredUIPromptForWindowsCredentialsW(&info, 0, &authPackage, credBuffer.data(), inBuffer, &pOut, &bufSize, NULL, CREDUIWIN_GENERIC | CREDUIWIN_IN_CRED_ONLY);
if (dwResult == ERROR_SUCCESS)
{
DWORD dwUserLength = 0;
DWORD dwDomainLength = 0;
DWORD dwPasswordLength = 0;
try
{
if (!::CredUnPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS, pOut, bufSize, nullptr, &dwUserLength, nullptr, &dwDomainLength, nullptr, &dwPasswordLength)
&& ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
{
std::vector<wchar_t> bufferUser(dwUserLength);
std::vector<wchar_t> bufferDomain(dwDomainLength);
std::vector<wchar_t> bufferPassword(dwPasswordLength);
if (::CredUnPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS, pOut, bufSize, bufferUser.data(), &dwUserLength, bufferDomain.data(), &dwDomainLength, bufferPassword.data(), &dwPasswordLength))
{
HANDLE hToken;
std::wstring strUsername = bufferUser.data();
std::wstring strDomain;
if (bufferDomain.size() == 0)
{
std::wstring::size_type pos = strUsername.find(L'\\');
if (pos != std::wstring::npos)
{
strDomain = strUsername.substr(0, pos);
strUsername = strUsername.substr(pos + 1, strUsername.size() - pos - 1);
}
}
else
{
strDomain = bufferDomain.data();
}
try
{
if (::LogonUserW(strUsername.c_str(), strDomain.c_str(), bufferPassword.data(), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken))
result = true;
}
catch (...) // Catch so memory can be cleared
{
}
ClearBuffer(bufferUser.data(), dwUserLength);
ClearBuffer(bufferDomain.data(), dwDomainLength);
ClearBuffer(bufferPassword.data(), dwPasswordLength);
}
}
}
catch(...) // Catch so memory can be cleared
{
}
ClearBuffer(pOut, bufSize);
CoTaskMemFree(pOut);
}
return result;
}
Is there a way to use CredUIPromptForWindowsCredentialsW without unpacking the buffer it returns to verify the login?

RegEnumKeyEx - Access violation writing location

The situation:
Need to fetch a list of all subkeys of a particular registry key.
Need to access both 32bit and 64bit software keys, so I cannot use the Registry namespace.
Using CSharp in .Net 3.5, and registry functionality from advapi32.dll
I have most of the functionality working but I'm stuck on an error. When it reaches a key that contains values, it will either skip it or throw the following error:
"Unhandled exception at 0x00C819CD in xxxxx.exe: 0xC0000005: Access violation writing location 0x00720077."
If the error occurs, it does not land in either of my catch statements. It hard crashes the program. From what I've read on the forums, I believe it may be an issue with it writing to protected memory but all of the examples I see are for C++
My Declaration (from P/Invoke Interop Assistant):
[DllImportAttribute("advapi32.dll", EntryPoint = "RegEnumKeyExW")]
public static extern int RegEnumKeyExW(
[InAttribute()] IntPtr hKey,
uint dwIndex,
[OutAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder lpName,
ref uint lpcchName,
IntPtr lpReserved,
IntPtr lpClass,
IntPtr lpcchClass,
IntPtr lpftLastWriteTime);
My Function (obviously a work in progress so it's a bit messy):
static private List<string> GetSubKeys(UIntPtr inHive, String inKeyName, RegSAM in32or64key) {
int hkey = 0;
uint dwIndex = 0;
long enumStatus = 0;
List<string> keys = new List<string>();
try {
uint lResult = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
inKeyName,
0,
(int)RegSAM.QueryValue | (int)RegSAM.EnumerateSubKeys | (int)in32or64key,
out hkey);
if (lResult == 0) {
while (enumStatus == ERROR_SUCCESS) {
StringBuilder lpName = new StringBuilder();
uint lpcchName = 256;
IntPtr lpReserved = IntPtr.Zero;
IntPtr lpClass = IntPtr.Zero;
IntPtr lpcchClass = IntPtr.Zero;
IntPtr lpftLastWriteTime = IntPtr.Zero;
enumStatus = RegEnumKeyExW(
(IntPtr)hkey,
dwIndex,
lpName,
ref lpcchName,
lpReserved,
lpClass,
lpcchClass,
lpftLastWriteTime);
switch (enumStatus) {
case ERROR_SUCCESS:
Console.WriteLine(string.Format("Key Found: {0}", lpName.ToString()));
break;
case ERROR_NO_MORE_ITEMS:
break;
default:
string error = new System.ComponentModel.Win32Exception((int)enumStatus).Message;
Console.WriteLine(string.Format("RegEnumKeyEx Error: {0}", error));
break;
}
dwIndex++;
}
} else {
Console.WriteLine(string.Format("RegOpenKey Error: {0}", lResult));
}
} catch (System.Runtime.InteropServices.COMException ex) {
Console.WriteLine(string.Format("COM Error: {0}", ex.Message));
} catch (Exception ex) {
Console.WriteLine(string.Format("Managed Error: {0}", ex.Message));
} finally {
if (0 != hkey) RegCloseKey(hkey);
}
return keys;
}
#endregion
StringBuilder lpName = new StringBuilder();
uint lpcchName = 256;
You are lying about the StringBuilder's capacity. It is 0, not 256. This will cause the pinvoke call to corrupt the GC heap. This eventually causes a hard crash, typically when a garbage collection takes place. Fix:
uint lpcchName = 256;
StringBuilder lpName = new StringBuilder(lpcchName);
Using the .NET RegistryKey.GetSubKeyNames() method instead would probably be wise.
Use the same way as. net4.0
static void Main(string[] args)
{
string displayName;
List<string> gInstalledSoftware = new List<string>();
using (var localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
{
var key = localMachine.OpenSubKey(#"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", false);
foreach (String keyName in key.GetSubKeyNames())
{
RegistryKey subkey = key.OpenSubKey(keyName);
displayName = subkey.GetValue("DisplayName") as string;
if (string.IsNullOrEmpty(displayName))
continue;
gInstalledSoftware.Add(displayName);
}
}
}
You can try using. net source code to solve this problem. etc.
public class RegistryKey:
IDisposable
{
internal static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(unchecked((int)0x80000000));
internal static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001));
internal static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002));
internal static readonly IntPtr HKEY_USERS = new IntPtr(unchecked((int)0x80000003));
internal static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(unchecked((int)0x80000004));
internal static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(unchecked((int)0x80000005));
private static readonly String[] hkeyNames = new String[] {
"HKEY_CLASSES_ROOT",
"HKEY_CURRENT_USER",
"HKEY_LOCAL_MACHINE",
"HKEY_USERS",
"HKEY_PERFORMANCE_DATA",
"HKEY_CURRENT_CONFIG",
};
public Object GetValue(String name)
{
return InternalGetValue(name, null, false, true);
}
internal Object InternalGetValue(String name, Object defaultValue, bool doNotExpand, bool checkSecurity)
{
Object data = defaultValue;
int type = 0;
int datasize = 0;
int ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, (byte[])null, ref datasize);
if (ret != 0)
{
if (IsPerfDataKey())
{
int size = 65000;
int sizeInput = size;
int r;
byte[] blob = new byte[size];
while (Win32Native.ERROR_MORE_DATA == (r = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref sizeInput)))
{
if (size == Int32.MaxValue)
{
// ERROR_MORE_DATA was returned however we cannot increase the buffer size beyond Int32.MaxValue
//Win32Error(r, name);
Console.WriteLine(string.Format("[{0}] [{1}]", r,name));
}
else if (size > (Int32.MaxValue / 2))
{
// at this point in the loop "size * 2" would cause an overflow
size = Int32.MaxValue;
}
else
{
size *= 2;
}
sizeInput = size;
blob = new byte[size];
}
if (r != 0)
Console.WriteLine(string.Format("[{0}] [{1}]", r, name));
return blob;
}
else
{
// For stuff like ERROR_FILE_NOT_FOUND, we want to return null (data).
// Some OS's returned ERROR_MORE_DATA even in success cases, so we
// want to continue on through the function.
if (ret != Win32Native.ERROR_MORE_DATA)
return data;
}
}
if (datasize < 0)
{
// unexpected code path
//BCLDebug.Assert(false, "[InternalGetValue] RegQueryValue returned ERROR_SUCCESS but gave a negative datasize");
datasize = 0;
}
switch (type)
{
case Win32Native.REG_NONE:
case Win32Native.REG_DWORD_BIG_ENDIAN:
case Win32Native.REG_BINARY:
{
byte[] blob = new byte[datasize];
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize);
data = blob;
}
break;
case Win32Native.REG_QWORD:
{ // also REG_QWORD_LITTLE_ENDIAN
if (datasize > 8)
{
// prevent an AV in the edge case that datasize is larger than sizeof(long)
goto case Win32Native.REG_BINARY;
}
long blob = 0;
//BCLDebug.Assert(datasize==8, "datasize==8");
// Here, datasize must be 8 when calling this
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, ref blob, ref datasize);
data = blob;
}
break;
case Win32Native.REG_DWORD:
{ // also REG_DWORD_LITTLE_ENDIAN
if (datasize > 4)
{
// prevent an AV in the edge case that datasize is larger than sizeof(int)
goto case Win32Native.REG_QWORD;
}
int blob = 0;
// Here, datasize must be four when calling this
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, ref blob, ref datasize);
data = blob;
}
break;
case Win32Native.REG_SZ:
{
if (datasize % 2 == 1)
{
// handle the case where the registry contains an odd-byte length (corrupt data?)
try
{
datasize = checked(datasize + 1);
}
catch (OverflowException e)
{
throw new IOException(("Arg_RegGetOverflowBug"), e);
}
}
char[] blob = new char[datasize / 2];
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize);
if (blob.Length > 0 && blob[blob.Length - 1] == (char)0)
{
data = new String(blob, 0, blob.Length - 1);
}
else
{
// in the very unlikely case the data is missing null termination,
// pass in the whole char[] to prevent truncating a character
data = new String(blob);
}
}
break;
case Win32Native.REG_EXPAND_SZ:
{
if (datasize % 2 == 1)
{
// handle the case where the registry contains an odd-byte length (corrupt data?)
try
{
datasize = checked(datasize + 1);
}
catch (OverflowException e)
{
throw new IOException(("Arg_RegGetOverflowBug"), e);
}
}
char[] blob = new char[datasize / 2];
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize);
if (blob.Length > 0 && blob[blob.Length - 1] == (char)0)
{
data = new String(blob, 0, blob.Length - 1);
}
else
{
// in the very unlikely case the data is missing null termination,
// pass in the whole char[] to prevent truncating a character
data = new String(blob);
}
if (!doNotExpand)
data = Environment.ExpandEnvironmentVariables((String)data);
}
break;
case Win32Native.REG_MULTI_SZ:
{
if (datasize % 2 == 1)
{
// handle the case where the registry contains an odd-byte length (corrupt data?)
try
{
datasize = checked(datasize + 1);
}
catch (OverflowException e)
{
throw new IOException(("Arg_RegGetOverflowBug"), e);
}
}
char[] blob = new char[datasize / 2];
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize);
// make sure the string is null terminated before processing the data
if (blob.Length > 0 && blob[blob.Length - 1] != (char)0)
{
try
{
char[] newBlob = new char[checked(blob.Length + 1)];
for (int i = 0; i < blob.Length; i++)
{
newBlob[i] = blob[i];
}
newBlob[newBlob.Length - 1] = (char)0;
blob = newBlob;
}
catch (OverflowException e)
{
throw new IOException(("Arg_RegGetOverflowBug"), e);
}
blob[blob.Length - 1] = (char)0;
}
IList<String> strings = new List<String>();
int cur = 0;
int len = blob.Length;
while (ret == 0 && cur < len)
{
int nextNull = cur;
while (nextNull < len && blob[nextNull] != (char)0)
{
nextNull++;
}
if (nextNull < len)
{
//BCLDebug.Assert(blob[nextNull] == (char)0, "blob[nextNull] should be 0");
if (nextNull - cur > 0)
{
strings.Add(new String(blob, cur, nextNull - cur));
}
else
{
// we found an empty string. But if we're at the end of the data,
// it's just the extra null terminator.
if (nextNull != len - 1)
strings.Add(String.Empty);
}
}
else
{
strings.Add(new String(blob, cur, len - cur));
}
cur = nextNull + 1;
}
data = new String[strings.Count];
strings.CopyTo((String[])data, 0);
}
break;
case Win32Native.REG_LINK:
default:
break;
}
return data;
}
public String[] GetSubKeyNames()
{
return InternalGetSubKeyNames();
}
public RegistryKey OpenSubKey(string name, bool writable=false)
{
name = FixupName(name); // Fixup multiple slashes to a single slash
SafeRegistryHandle result = null;
int ret = Win32Native.RegOpenKeyEx(hkey,
name,
0,
GetRegistryKeyAccess(writable) | (int)regView,
out result);
if (ret == 0 && !result.IsInvalid)
{
RegistryKey key = new RegistryKey(result, writable, false, remoteKey, false, regView);
key.checkMode = GetSubKeyPermissonCheck(writable);
key.keyName = keyName + "\\" + name;
return key;
}
// Return null if we didn't find the key.
if (ret == Win32Native.ERROR_ACCESS_DENIED || ret == Win32Native.ERROR_BAD_IMPERSONATION_LEVEL)
{
// We need to throw SecurityException here for compatibility reasons,
// although UnauthorizedAccessException will make more sense.
//ThrowHelper.ThrowSecurityException(ExceptionResource.Security_RegistryPermission);
}
return null;
}
private const int MaxKeyLength = 255;
internal unsafe String[] InternalGetSubKeyNames()
{
int subkeys = InternalSubKeyCount();
String[] names = new String[subkeys]; // Returns 0-length array if empty.
if (subkeys > 0)
{
char[] name = new char[MaxKeyLength + 1];
int namelen;
fixed (char* namePtr = &name[0])
{
for (int i = 0; i < subkeys; i++)
{
namelen = name.Length; // Don't remove this. The API's doesn't work if this is not properly initialised.
int ret = Win32Native.RegEnumKeyEx(hkey,
i,
namePtr,
ref namelen,
null,
null,
null,
null);
if (ret != 0)
//Win32Error(ret, null);
Console.WriteLine(ret);
names[i] = new String(namePtr);
}
}
}
return names;
}
internal int InternalSubKeyCount()
{
int subkeys = 0;
int junk = 0;
int ret = Win32Native.RegQueryInfoKey(hkey,
null,
null,
IntPtr.Zero,
ref subkeys, // subkeys
null,
null,
ref junk, // values
null,
null,
null,
null);
if (ret != 0)
//Win32Error(ret, null);
Console.WriteLine(ret);
return subkeys;
}
public static RegistryKey OpenBaseKey(RegistryHive hKey, RegistryView view)
{
return GetBaseKey((IntPtr)((int)hKey), view);
}
internal static RegistryKey GetBaseKey(IntPtr hKey, RegistryView view)
{
int index = ((int)hKey) & 0x0FFFFFFF;
//BCLDebug.Assert(index >= 0 && index < hkeyNames.Length, "index is out of range!");
//BCLDebug.Assert((((int)hKey) & 0xFFFFFFF0) == 0x80000000, "Invalid hkey value!");
bool isPerf = hKey == HKEY_PERFORMANCE_DATA;
// only mark the SafeHandle as ownsHandle if the key is HKEY_PERFORMANCE_DATA.
SafeRegistryHandle srh = new SafeRegistryHandle(hKey, isPerf);
RegistryKey key = new RegistryKey(srh, true, true, false, isPerf, view);
key.checkMode = RegistryKeyPermissionCheck.Default;
key.keyName = hkeyNames[index];
return key;
}
private volatile SafeRegistryHandle hkey = null;
private volatile int state = 0;
private volatile String keyName;
private volatile bool remoteKey = false;
private volatile RegistryKeyPermissionCheck checkMode;
private volatile RegistryView regView = RegistryView.Default;
private const int STATE_DIRTY = 0x0001;
// SystemKey indicates that this is a "SYSTEMKEY" and shouldn't be "opened"
// or "closed".
//
private const int STATE_SYSTEMKEY = 0x0002;
// Access
//
private const int STATE_WRITEACCESS = 0x0004;
// Indicates if this key is for HKEY_PERFORMANCE_DATA
private const int STATE_PERF_DATA = 0x0008;
private RegistryKey(SafeRegistryHandle hkey, bool writable, bool systemkey, bool remoteKey, bool isPerfData, RegistryView view)
{
this.hkey = hkey;
this.keyName = "";
this.remoteKey = remoteKey;
this.regView = view;
if (systemkey)
{
this.state |= STATE_SYSTEMKEY;
}
if (writable)
{
this.state |= STATE_WRITEACCESS;
}
if (isPerfData)
this.state |= STATE_PERF_DATA;
}
private RegistryKeyPermissionCheck GetSubKeyPermissonCheck(bool subkeyWritable)
{
if (checkMode == RegistryKeyPermissionCheck.Default)
{
return checkMode;
}
if (subkeyWritable)
{
return RegistryKeyPermissionCheck.ReadWriteSubTree;
}
else
{
return RegistryKeyPermissionCheck.ReadSubTree;
}
}
static int GetRegistryKeyAccess(bool isWritable)
{
int winAccess;
if (!isWritable)
{
winAccess = Win32Native.KEY_READ;
}
else
{
winAccess = Win32Native.KEY_READ | Win32Native.KEY_WRITE;
}
return winAccess;
}
internal static String FixupName(String name)
{
//BCLDebug.Assert(name!=null,"[FixupName]name!=null");
if (name.IndexOf('\\') == -1)
return name;
StringBuilder sb = new StringBuilder(name);
FixupPath(sb);
int temp = sb.Length - 1;
if (temp >= 0 && sb[temp] == '\\') // Remove trailing slash
sb.Length = temp;
return sb.ToString();
}
private static void FixupPath(StringBuilder path)
{
//Contract.Requires(path != null);
int length = path.Length;
bool fixup = false;
char markerChar = (char)0xFFFF;
int i = 1;
while (i < length - 1)
{
if (path[i] == '\\')
{
i++;
while (i < length)
{
if (path[i] == '\\')
{
path[i] = markerChar;
i++;
fixup = true;
}
else
break;
}
}
i++;
}
if (fixup)
{
i = 0;
int j = 0;
while (i < length)
{
if (path[i] == markerChar)
{
i++;
continue;
}
path[j] = path[i];
i++;
j++;
}
path.Length += j - i;
}
}
#region IDisposable Support
private bool disposedValue = false; // 要检测冗余调用
public void Close()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (hkey != null)
{
if (!IsSystemKey())
{
try
{
hkey.Dispose();
}
catch (IOException)
{
// we don't really care if the handle is invalid at this point
}
finally
{
hkey = null;
}
}
else if (disposing && IsPerfDataKey())
{
// System keys should never be closed. However, we want to call RegCloseKey
// on HKEY_PERFORMANCE_DATA when called from PerformanceCounter.CloseSharedResources
// (i.e. when disposing is true) so that we release the PERFLIB cache and cause it
// to be refreshed (by re-reading the registry) when accessed subsequently.
// This is the only way we can see the just installed perf counter.
// NOTE: since HKEY_PERFORMANCE_DATA is process wide, there is inherent race condition in closing
// the key asynchronously. While Vista is smart enough to rebuild the PERFLIB resources
// in this situation the down level OSes are not. We have a small window between
// the dispose below and usage elsewhere (other threads). This is By Design.
// This is less of an issue when OS > NT5 (i.e Vista & higher), we can close the perfkey
// (to release & refresh PERFLIB resources) and the OS will rebuild PERFLIB as necessary.
SafeRegistryHandle.RegCloseKey(RegistryKey.HKEY_PERFORMANCE_DATA);
}
}
}
private bool IsPerfDataKey()
{
return (this.state & STATE_PERF_DATA) != 0;
}
private bool IsSystemKey()
{
return (this.state & STATE_SYSTEMKEY) != 0;
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
[System.Security.SecurityCritical]
public sealed class SafeRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[System.Security.SecurityCritical]
internal SafeRegistryHandle() : base(true) { }
[System.Security.SecurityCritical]
public SafeRegistryHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle)
{
SetHandle(preexistingHandle);
}
[System.Security.SecurityCritical]
override protected bool ReleaseHandle()
{
return (RegCloseKey(handle) == Win32Native.ERROR_SUCCESS);
}
[DllImport(Win32Native.ADVAPI32)]
internal static extern int RegCloseKey(IntPtr hKey);
}
[Serializable]
[System.Runtime.InteropServices.ComVisible(true)]
public enum RegistryHive
{
ClassesRoot = unchecked((int)0x80000000),
CurrentUser = unchecked((int)0x80000001),
LocalMachine = unchecked((int)0x80000002),
Users = unchecked((int)0x80000003),
PerformanceData = unchecked((int)0x80000004),
CurrentConfig = unchecked((int)0x80000005),
}
public enum RegistryView
{
Default = 0, // 0x0000 operate on the default registry view
Registry64 = Win32Native.KEY_WOW64_64KEY, // 0x0100 operate on the 64-bit registry view
Registry32 = Win32Native.KEY_WOW64_32KEY, // 0x0200 operate on the 32-bit registry view
};
public enum RegistryKeyPermissionCheck
{
Default = 0,
ReadSubTree = 1,
ReadWriteSubTree = 2
}
public static class Win32Native
{
internal const String ADVAPI32 = "advapi32.dll";
internal const int KEY_WOW64_64KEY = 0x0100; //
internal const int KEY_WOW64_32KEY = 0x0200; //
internal const int ERROR_SUCCESS = 0x0;
internal const int READ_CONTROL = 0x00020000;
internal const int SYNCHRONIZE = 0x00100000;
internal const int STANDARD_RIGHTS_READ = READ_CONTROL;
internal const int STANDARD_RIGHTS_WRITE = READ_CONTROL;
internal const int KEY_QUERY_VALUE = 0x0001;
internal const int KEY_SET_VALUE = 0x0002;
internal const int KEY_CREATE_SUB_KEY = 0x0004;
internal const int KEY_ENUMERATE_SUB_KEYS = 0x0008;
internal const int KEY_NOTIFY = 0x0010;
internal const int KEY_CREATE_LINK = 0x0020;
internal const int KEY_READ = ((STANDARD_RIGHTS_READ |
KEY_QUERY_VALUE |
KEY_ENUMERATE_SUB_KEYS |
KEY_NOTIFY)
&
(~SYNCHRONIZE));
internal const int KEY_WRITE = ((STANDARD_RIGHTS_WRITE |
KEY_SET_VALUE |
KEY_CREATE_SUB_KEY)
&
(~SYNCHRONIZE));
internal const int ERROR_ACCESS_DENIED = 0x5;
internal const int ERROR_BAD_IMPERSONATION_LEVEL = 0x542;
[DllImport(ADVAPI32, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern int RegOpenKeyEx(SafeRegistryHandle hKey, String lpSubKey,
int ulOptions, int samDesired, out SafeRegistryHandle hkResult);
[DllImport(ADVAPI32, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern int RegQueryInfoKey(SafeRegistryHandle hKey, [Out]StringBuilder lpClass,
int[] lpcbClass, IntPtr lpReserved_MustBeZero, ref int lpcSubKeys,
int[] lpcbMaxSubKeyLen, int[] lpcbMaxClassLen,
ref int lpcValues, int[] lpcbMaxValueNameLen,
int[] lpcbMaxValueLen, int[] lpcbSecurityDescriptor,
int[] lpftLastWriteTime);
[DllImport(ADVAPI32, CharSet = CharSet.Auto, BestFitMapping = false)]
internal unsafe static extern int RegEnumKeyEx(SafeRegistryHandle hKey, int dwIndex,
char* lpName, ref int lpcbName, int[] lpReserved,
[Out]StringBuilder lpClass, int[] lpcbClass,
long[] lpftLastWriteTime);
internal const int ERROR_MORE_DATA = 0xEA;
internal const int REG_NONE = 0; // No value type
internal const int REG_DWORD_BIG_ENDIAN = 5; // 32-bit number
internal const int REG_BINARY = 3; // Free form binary
internal const int REG_QWORD = 11; // 64-bit number
internal const int REG_DWORD = 4; // 32-bit number
internal const int REG_SZ = 1; // Unicode nul terminated string
internal const int REG_EXPAND_SZ = 2; // Unicode nul terminated string
internal const int REG_MULTI_SZ = 7; // Multiple Unicode strings
internal const int REG_LINK = 6; // Symbolic Link (unicode)
[DllImport(ADVAPI32, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern int RegQueryValueEx(SafeRegistryHandle hKey, String lpValueName,
int[] lpReserved, ref int lpType, [Out] byte[] lpData,
ref int lpcbData);
[DllImport(ADVAPI32, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern int RegQueryValueEx(SafeRegistryHandle hKey, String lpValueName,
int[] lpReserved, ref int lpType, ref int lpData,
ref int lpcbData);
[DllImport(ADVAPI32, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern int RegQueryValueEx(SafeRegistryHandle hKey, String lpValueName,
int[] lpReserved, ref int lpType, ref long lpData,
ref int lpcbData);
[DllImport(ADVAPI32, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern int RegQueryValueEx(SafeRegistryHandle hKey, String lpValueName,
int[] lpReserved, ref int lpType, [Out] char[] lpData,
ref int lpcbData);
[DllImport(ADVAPI32, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern int RegQueryValueEx(SafeRegistryHandle hKey, String lpValueName,
int[] lpReserved, ref int lpType, [Out]StringBuilder lpData,
ref int lpcbData);
}

Marshal.PtrToStructure (and back again) and generic solution for endianness swapping

I have a system where a remote agent sends serialized structures (from an embedded C system) for me to read and store via IP/UDP. In some cases I need to send back the same structure types. I thought I had a nice setup using Marshal.PtrToStructure (receive) and Marshal.StructureToPtr (send). However, a small gotcha is that the network big endian integers need to be converted to my x86 little endian format to be used locally. When I'm sending them off again, big endian is the way to go.
Here are the functions in question:
private static T BytesToStruct<T>(ref byte[] rawData) where T: struct
{
T result = default(T);
GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
try
{
IntPtr rawDataPtr = handle.AddrOfPinnedObject();
result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));
}
finally
{
handle.Free();
}
return result;
}
private static byte[] StructToBytes<T>(T data) where T: struct
{
byte[] rawData = new byte[Marshal.SizeOf(data)];
GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
try
{
IntPtr rawDataPtr = handle.AddrOfPinnedObject();
Marshal.StructureToPtr(data, rawDataPtr, false);
}
finally
{
handle.Free();
}
return rawData;
}
And a quick example structure that might be used like this:
byte[] data = this.sock.Receive(ref this.ipep);
Request request = BytesToStruct<Request>(ref data);
Where the structure in question looks like:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct Request
{
public byte type;
public short sequence;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public byte[] address;
}
What (generic) way can I swap the endianness when marshalling the structures? My need is such that the locally stored 'request.sequence' in this example should be little-endian for displaying to the user. I don't want to have to swap the endianness in a structure-specific way since it's a generic problem.
My first thought was to use Reflection, but I'm not very familiar with that feature. Also, I hoped that there would be a better solution out there that somebody could point me towards. Thanks in advance :)
Reflection does seem like the only real way to accomplish what you're after.
I've put together some code below. It creates an attribute called EndianAttribute that can be applied at the field level on a struct. I've included the definition for this attribute and it's associated enum, as well as the modifications to your code necessary to use it.
As a side note, you did not need to define rawData as a ref parameter.
Note that this does require the use of C# 3.0/.NET 3.5, since I'm using LINQ and anonymous types in the function doing the work. It would not be difficult to rewrite the function without these features, though.
[AttributeUsage(AttributeTargets.Field)]
public class EndianAttribute : Attribute
{
public Endianness Endianness { get; private set; }
public EndianAttribute(Endianness endianness)
{
this.Endianness = endianness;
}
}
public enum Endianness
{
BigEndian,
LittleEndian
}
private static void RespectEndianness(Type type, byte[] data)
{
var fields = type.GetFields().Where(f => f.IsDefined(typeof(EndianAttribute), false))
.Select(f => new
{
Field = f,
Attribute = (EndianAttribute)f.GetCustomAttributes(typeof(EndianAttribute), false)[0],
Offset = Marshal.OffsetOf(type, f.Name).ToInt32()
}).ToList();
foreach (var field in fields)
{
if ((field.Attribute.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) ||
(field.Attribute.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian))
{
Array.Reverse(data, field.Offset, Marshal.SizeOf(field.Field.FieldType));
}
}
}
private static T BytesToStruct<T>(byte[] rawData) where T : struct
{
T result = default(T);
RespectEndianness(typeof(T), rawData);
GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
try
{
IntPtr rawDataPtr = handle.AddrOfPinnedObject();
result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));
}
finally
{
handle.Free();
}
return result;
}
private static byte[] StructToBytes<T>(T data) where T : struct
{
byte[] rawData = new byte[Marshal.SizeOf(data)];
GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
try
{
IntPtr rawDataPtr = handle.AddrOfPinnedObject();
Marshal.StructureToPtr(data, rawDataPtr, false);
}
finally
{
handle.Free();
}
RespectEndianness(typeof(T), rawData);
return rawData;
}
For those of us without Linq, a replacement RespectEndianness():
private static void RespectEndianness(Type type, byte[] data) {
foreach (FieldInfo f in type.GetFields()) {
if (f.IsDefined(typeof(EndianAttribute), false)) {
EndianAttribute att = (EndianAttribute)f.GetCustomAttributes(typeof(EndianAttribute), false)[0];
int offset = Marshal.OffsetOf(type, f.Name).ToInt32();
if ((att.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) ||
(att.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian)) {
Array.Reverse(data, offset, Marshal.SizeOf(f.FieldType));
}
}
}
}
Here's my variation - it handles nested structs and arrays, with the assumption that arrays are of a fixed size, eg marked with a [MarshalAs(UnmanagedType.ByValArray, SizeConst = N)] attribute.
public static class Serializer
{
public static byte[] GetBytes<T>(T structure, bool respectEndianness = true) where T : struct
{
var size = Marshal.SizeOf(structure); //or Marshal.SizeOf<T>(); in .net 4.5.1
var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(structure, ptr, true);
Marshal.Copy(ptr, bytes, 0, size);
Marshal.FreeHGlobal(ptr);
if (respectEndianness) RespectEndianness(typeof(T), bytes);
return bytes;
}
public static T FromBytes<T>(byte[] bytes, bool respectEndianness = true) where T : struct
{
var structure = new T();
if (respectEndianness) RespectEndianness(typeof(T), bytes);
int size = Marshal.SizeOf(structure); //or Marshal.SizeOf<T>(); in .net 4.5.1
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(bytes, 0, ptr, size);
structure = (T)Marshal.PtrToStructure(ptr, structure.GetType());
Marshal.FreeHGlobal(ptr);
return structure;
}
private static void RespectEndianness(Type type, byte[] data, int offSet = 0)
{
var fields = type.GetFields()
.Select(f => new
{
Field = f,
Offset = Marshal.OffsetOf(type, f.Name).ToInt32(),
}).ToList();
foreach (var field in fields)
{
if (field.Field.FieldType.IsArray)
{
//handle arrays, assuming fixed length
var attr = field.Field.GetCustomAttributes(typeof(MarshalAsAttribute), false).FirstOrDefault();
var marshalAsAttribute = attr as MarshalAsAttribute;
if (marshalAsAttribute == null || marshalAsAttribute.SizeConst == 0)
throw new NotSupportedException(
"Array fields must be decorated with a MarshalAsAttribute with SizeConst specified.");
var arrayLength = marshalAsAttribute.SizeConst;
var elementType = field.Field.FieldType.GetElementType();
var elementSize = Marshal.SizeOf(elementType);
var arrayOffset = field.Offset + offSet;
for (int i = arrayOffset; i < arrayOffset + elementSize * arrayLength; i += elementSize) {
RespectEndianness(elementType, data, i);
}
}
else if (!field.Field.FieldType.IsPrimitive) //or !field.Field.FiledType.GetFields().Length == 0
{
//handle nested structs
RespectEndianness(field.Field.FieldType, data, field.Offset);
}
else
{
//handle primitive types
Array.Reverse(data, offSet + field.Offset, Marshal.SizeOf(field.Field.FieldType));
}
}
}
}
This question was awesome and helped me a lot! I needed to expand on the endian changer though as it doesn't seem to handle arrays or structs within structs.
public struct mytest
{
public int myint;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public int[] ptime;
}
public static void SwapIt(Type type, byte[] recvbyte, int offset)
{
foreach (System.Reflection.FieldInfo fi in type.GetFields())
{
int index = Marshal.OffsetOf(type, fi.Name).ToInt32() + offset;
if (fi.FieldType == typeof(int))
{
Array.Reverse(recvbyte, index, sizeof(int));
}
else if (fi.FieldType == typeof(float))
{
Array.Reverse(recvbyte, index, sizeof(float));
}
else if (fi.FieldType == typeof(double))
{
Array.Reverse(recvbyte, index, sizeof(double));
}
else
{
// Maybe we have an array
if (fi.FieldType.IsArray)
{
// Check for MarshalAs attribute to get array size
object[] ca = fi.GetCustomAttributes(false);
if (ca.Count() > 0 && ca[0] is MarshalAsAttribute)
{
int size = ((MarshalAsAttribute)ca[0]).SizeConst;
// Need to use GetElementType to see that int[] is made of ints
if (fi.FieldType.GetElementType() == typeof(int))
{
for (int i = 0; i < size; i++)
{
Array.Reverse(recvbyte, index + (i * sizeof(int)), sizeof(int));
}
}
else if (fi.FieldType.GetElementType() == typeof(float))
{
for (int i = 0; i < size; i++)
{
Array.Reverse(recvbyte, index + (i * sizeof(float)), sizeof(float));
}
}
else if (fi.FieldType.GetElementType() == typeof(double))
{
for (int i = 0; i < size; i++)
{
Array.Reverse(recvbyte, index + (i * sizeof(double)), sizeof(double));
}
}
else
{
// An array of something else?
Type t = fi.FieldType.GetElementType();
int s = Marshal.SizeOf(t);
for (int i = 0; i < size; i++)
{
SwapIt(t, recvbyte, index + (i * s));
}
}
}
}
else
{
SwapIt(fi.FieldType, recvbyte, index);
}
}
}
}
Note this code was only tested on structs made of int, float, double. Will probably mess up if you have a string in there!

Pinvoke problem with the Windows helper API

This is my first serious attempt at P/invoke. This is a console app I'm using to work out the code for a web service that will hopefully return a mac address from an ip address or fully qualified system name. The code compiles. However when run without args, I get an unhandled exception-IndexOutOfRangeException. If I run it with args, I get a different unhandled exception, AccessViolationException: Attempted to read or write protected memory. And, I'm self-taught...Please be kind if I've done something stupid. Here's my code:
using System;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
namespace GetMac
{
class Program
{
const int NET_STRING_IPV4_ADDRESS = 1; // 192.168.100.10
const int NET_STRING_IPV4_SERVICE = 2; // 192.168.100.10:80
const int NET_STRING_IPV4_NETWORK = 4; // 192.168.100/24
const int NET_STRING_IPV6_ADDRESS = 8; // 21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A%2
const int NET_STRING_IPV6_ADDRESS_NO_SCOPE = 8; // 21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A
const int NET_STRING_IPV6_SERVICE = 32; // [21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A%2]:8080
const int NET_STRING_IPV6_SERVICE_NO_SCOPE = 64; // 21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A:8080
const int NET_STRING_IPV6_NETWORK = 128; // 21DA:D3::/48
const int NET_STRING_NAMED_ADDRESS = 256; // www.microsoft.com
const int NET_STRING_NAMED_SERVICE = 512; // www.microsoft.com:80
internal static class NativeMethods
{
[DllImport("IpHlpApi.dll", CharSet = CharSet.Unicode)]
//public static extern uint ParseNetworkString(ref string networkString, int types, out NetAddressInfo addressInfo, out ushort portNumber, out byte prefixLength);
public static extern uint ParseNetworkString(string networkString, int types);
}
public static string GetMacAddress(IPAddress ipAddress)
{
string strMacAddress = string.Empty;
try
{
char delimChar = '=';
string strTempMacAddress = string.Empty;
ProcessStartInfo objProcessStartInfo = new ProcessStartInfo();
Process objProcess = new Process();
objProcessStartInfo.FileName = "nbtstat";
objProcessStartInfo.RedirectStandardInput = false;
objProcessStartInfo.RedirectStandardOutput = true;
objProcessStartInfo.Arguments = "-A " + ipAddress;
objProcessStartInfo.UseShellExecute = false;
objProcess = Process.Start(objProcessStartInfo);
int Counter = -1;
while (Counter <= -1)
{
Counter = strTempMacAddress.Trim().ToLower().IndexOf("mac address", 0);
if (Counter > -1)
{
break;
}
strTempMacAddress = objProcess.StandardOutput.ReadLine();
}
objProcess.WaitForExit();
string[] words = strTempMacAddress.Split(delimChar);
strTempMacAddress = words[1];
strMacAddress = strTempMacAddress.Trim();
strMacAddress = strMacAddress.Replace('-', ':');
}
catch (Exception Ex)
{
Console.WriteLine(Ex.ToString());
Console.ReadLine();
}
return strMacAddress;
}
static void Main(string[] args)
{
string macAddress = null;
IPAddress ipAddress = null;
string strHostOrIp;
if (args[0] == null)
{
Console.WriteLine("Please enter an IP address or fully qualified host name");
return;
}
else
{
strHostOrIp = args[0];
}
if (NativeMethods.ParseNetworkString(strHostOrIp, NET_STRING_IPV4_ADDRESS) == 0) //true if it's an IP address
{
IPAddress address = IPAddress.Parse(strHostOrIp);
ipAddress = address;
}
else if (NativeMethods.ParseNetworkString(strHostOrIp, NET_STRING_NAMED_SERVICE) == 0) //true if it's a fully qualified host name
{
IPHostEntry hostEntry = null;
try
{
hostEntry = Dns.GetHostEntry(strHostOrIp);
}
catch
{
return;
}
if (hostEntry.AddressList.Length == 0)
{
return;
}
foreach (IPAddress ip in hostEntry.AddressList)
{
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) // true if we have IPv4
{
ipAddress = ip;
break;
}
}
}
else
{
ipAddress = null;
}
macAddress = GetMacAddress(ipAddress);
Console.WriteLine(macAddress);
}
}
}
// DWORD WINAPI ParseNetworkString( -in const WCHAR *NetworkString,
// -in DWORD Types,
// -out_opt PNET_ADDRESS_INFO AddressInfo,
// -out_opt USHORT *PortNumber,
// -out_opt BYTE *PrefixLength);
// ParseNetworkString function parses the input network string and checks whether it is a legal
// representation of the specified IP network string type. If the string matches a type and its
// specification, the function can optionally return the parsed result
// Return Value
// If the function succeeds, the return value is ERROR_SUCCESS. // I think this means it returns 0.
// If the function fails, the return value is one of the following error codes.
// ERROR_INSUFFICIENT_BUFFER - The buffer passed to the function is too small. This error is returned if the buffer pointed to by the AddressInfo parameter is too small to hold the parsed network address.
// ERROR_INVALID_PARAMETER - An invalid parameter was passed to the function. This error is returned if a NULL pointer is passed in the NetworkString parameter
// http://msdn.microsoft.com/en-us/library/bb408412(VS.85).aspx
Thanx in advance.
This is a "more" correct DllImport:
[DllImport("iphlpapi.dll", CharSet = CharSet.Unicode)]
public static extern uint ParseNetworkString(string networkString, uint types, IntPtr addressInfo, IntPtr portNumber, IntPtr prefixLength);
Example Usage:
Console.WriteLine(ParseNetworkString("192.168.100.10:20", NET_STRING_IPV4_SERVICE, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero));
The three "optional" parameters to ParseNetworkString are not optional in the sense that you can omit them. You must provide them, but you can pass null (or zero) to say that you don't want that particular value returned.

Categories

Resources