Related
I'm trying to set-up an easy 1-click change of the mode of my screens (extended <--> disconnected)
but my screens are assigned no ID by the QueryDisplayConfig method.
(I'm using the User32 PInvoke lib from https://github.com/AArnott/pinvoke, in addition to what you can find in the code below)
I tried:
stepping through the code with breakpoints, making sure EVERY value is the default value.
elevating VS to run with administrator privileges.
making extra sure that the flags and errors are functioning correctly.
[DllImport("User32.dll")]
public static extern int GetDisplayConfigBufferSizes(uint flags, ref uint numPathArrayElements, ref uint numModeInfoArrayElements);
[DllImport("User32.dll")]
public static extern int QueryDisplayConfig(
uint flags,
ref uint numPathArrayElements, DISPLAYCONFIG_PATH_INFO[] pathArray,
ref uint numModeInfoArrayElements, DISPLAYCONFIG_MODE_INFO[] modeInfoArray,
DISPLAYCONFIG_TOPOLOGY_ID[] currentTopologyId
);
const int QDC_ALL_PATHS = 1;
const int QDC_ONLY_ACTIVE_PATHS = 2;
const int QDC_DATABASE_CURRENT = 4;
public static void CheckDisplays() {
uint numPathArrayElements = 0;
uint numModeInfoArrayElements = 0;
uint filter = QDC_ONLY_ACTIVE_PATHS;
int bufferError = GetDisplayConfigBufferSizes(filter, ref numPathArrayElements, ref numModeInfoArrayElements);
DISPLAYCONFIG_PATH_INFO[] pathArray = new DISPLAYCONFIG_PATH_INFO[numPathArrayElements];
DISPLAYCONFIG_MODE_INFO[] modeArray = new DISPLAYCONFIG_MODE_INFO[numModeInfoArrayElements];
int queryError = QueryDisplayConfig(filter, ref numPathArrayElements, pathArray, ref numModeInfoArrayElements, modeArray, null);
Console.WriteLine();
Console.WriteLine("Elements: " + numPathArrayElements); // Prints the correct amount of connected screens.
Console.WriteLine("BUFFER ERROR: " + bufferError); // Prints 0 -- as in Success.
Console.WriteLine("PATH ERROR: " + queryError); // Prints 0 -- as in Success.
for (int i = 0; i < pathArray.Length; i++) {
if (pathArray[i].sourceInfo.id != 0) { Console.WriteLine($"Path{i} has been initialized correctly!!"); }
// Every object in the array has default values and IDs of 0.
// Nothing prints here.
}
}
Each screen should be assigned an ID and a proper mode.
Instead, everything has the default value and I seem to be stuck.
Your declaration for QueryDisplayConfig is wrong : arrays must be [Out]
This works for me :
(I have only 1 monitor but the values received in arrays are the same as in C++ (I translated structures from SDK headers))
(IntPtr.Zero for currentTopologyId with QDC_ONLY_ACTIVE_PATHS)
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int QueryDisplayConfig(uint flags, ref uint numPathArrayElements, [Out] DISPLAYCONFIG_PATH_INFO[] pathArray,
ref uint modeInfoArrayElements, [Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, IntPtr currentTopologyId);
as i was trying to have a test and learn about native p/invoke functions i was trying to use only pinvoke and then compare the time it takes to get process info with .net simple
Process myProc = Process.GetProcessByName("WinRAR");
though i feel that i need to realy measure that almost 2 pages in length code, using P/invoke just so i could get same results, but this time ONLY with native code, i guess that it should be faster and i want to atleast get to benchmark both ,so please help here .
so it seems that my code is 1) ... ok i guess i could count to 20
"enumerating" all it's issues, but mainly :
it doesn't enumerate all processes for a strange reason i did not see winrar for instance
second it is far from being as short as pinvoke bunche-of-methods needs
(i am using Winforms app, though you could hard code the ProcessName needed in order to "search" for the correct process)
most of comments here is by the author of well, most parts of the code
i only modified it a little to have enum later so you could choose between searching via window title or process name
so this is the code:
main entry - create instance of class :
pinvokers Pi = new pinvokers();
// Find all Internet Explorer instances(i used winrar, as my second task in this project is also test application performance... later on, and again, using only native calls)
Pi.FindWindows(0, pinvokers.SearchWin.ProcName, null, new Regex(TBX_SelectedWinName.Text), new pinvokers.FoundWindowCallback(pinvokers.foundWindowToPrint));
public class pinvokers
{
// Win32 constants.
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[DllImport("user32.Dll")]
private static extern Boolean EnumChildWindows(int hWndParent, PChildCallBack lpEnumFunc, int lParam);
[DllImport("user32.Dll")]
private static extern int GetWindowText(int hWnd, StringBuilder text, int count);
[DllImport("user32.Dll")]
private static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
[DllImport("user32.Dll")]
private static extern Int32 SendMessage(int hWnd, int Msg, int wParam, StringBuilder lParam);
[DllImport("user32.Dll")]
private static extern Int32 SendMessage(int hWnd, int Msg, int wParam, int lParam);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint GetWindowModuleFileName(IntPtr hwnd,
StringBuilder lpszFileName, uint cchFileNameMax);
[DllImport("psapi.dll")]
private static extern uint GetModuleFileNameEx(IntPtr hWnd, IntPtr hModule, StringBuilder lpFileName, int nSize);
// The PChildCallBack delegate that we used with EnumWindows.
private delegate bool PChildCallBack(int hWnd, int lParam);
// This is an event that is run each time a window was found that matches the search criterias. The boolean
// return value of the delegate matches the functionality of the PChildCallBack delegate function.
static event FoundWindowCallback foundWindowCB;
public delegate bool FoundWindowCallback(int hWnd);
int parentHandle;
Regex process;
#region <<=========== not nedded - search by window title. i am looking to search via process name ===========>>
/* <- commented all unsuesd
Regex windowText;
public static bool foundWindowToPrint(int handle)
{
// Print the window info.
printWindowInfo(handle);
// Continue on with next window.
return true;
}
static void printWindowInfo(int handle)
{
// Get the text.
int txtLength = SendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
StringBuilder sbText = new StringBuilder(txtLength + 1);
SendMessage(handle, WM_GETTEXT, sbText.Capacity, sbText);
// Now we can write out the information we have on the window.
MessageBox.Show("Handle: " + handle);
MessageBox.Show("Text : " + sbText);
}
=====>end of un needed search bywindowtitle1
*/
#endregion
// my plan was to use enum instead of if !empty or null value for ither title name or process name so that's how the original code ditermin wich one to execute.
public enum SearchWin
{
Title, ProcName
}
//first method (and that's all i could really tell.. as it is full of callbacks and private extern, and delegates ... so complex
public void FindWindows(int parentHandle, SearchWin By, Regex windowText, Regex process, FoundWindowCallback fwc)
{
this.parentHandle = parentHandle;
//this.windowText = windowText;
this.process = process;
// Add the FounWindowCallback to the foundWindow event.
foundWindowCB = fwc;
// Invoke the EnumChildWindows function.
EnumChildWindows(parentHandle, new PChildCallBack(enumChildWindowsCallback), 0);
}
// This function gets called each time a window is found by the EnumChildWindows function. The foun windows here
// are NOT the final found windows as the only filtering done by EnumChildWindows is on the parent window handle.
private bool enumChildWindowsCallback(int handle, int lParam)
{
#region <<=========== not nedded - search by window title. #2 ===========>>
/* <--here too window title portion of code commented
// If a window text was provided, check to see if it matches the window.
if (windowText != null)
{
int txtLength = SendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
StringBuilder sbText = new StringBuilder(txtLength + 1);
SendMessage(handle, WM_GETTEXT, sbText.Capacity, sbText);
// If it does not match, return true so we can continue on with the next window.
if (!windowText.IsMatch(sbText.ToString()))
return true;
}
*/
#endregion //endr2
// If a process name was provided, check to see if it matches the window.
if (process != null)
{
int processID;
GetWindowThreadProcessId(handle, out processID);
// Now that we have the process ID, we can use the built in .NET function to obtain a process object.
var ProcessName = GetProcNameByID(processID);
// If it does not match, return true so we can continue on with the next window.
if (!process.IsMatch(ProcessName))
return true;
}
// If we get to this point, the window is a match. Now invoke the foundWindow event and based upon
// the return value, whether we should continue to search for windows.
return foundWindowCB(handle);
}
private string GetProcNameByID(int ProcID)
{
IntPtr hProcess = OpenProcess(0x0410, false, ProcID);
StringBuilder text = new StringBuilder(1000);
GetWindowModuleFileName(hProcess, text, (uint)text.Capacity);
//GetModuleFileNameEx(hProcess, IntPtr.Zero, text, text.Capacity);
//CloseHandle(hProcess); here i am trying to catch what enumeration of windows got in its net , all this code does work just copy and paste it .
var t = text.ToString();
if (t.ToLower().Contains("inra"))
MessageBox.Show(t);
return t;
}
}
so could this be a little shorter is a side question
main one is :
Why does it not enumerate all the processes ?
i don't know if it is the best i could get or maybe someone who knows what he is doing with win api, or p/invoke or if i had to try and make unmanagedLand win over .net built in calsses
i might have rolled my sleeves and put some c++ code together (will probbably take another week)
and compile it to a dll to get all functions together in one DLL (should it do some perfomance gain)
and then i might have cut some gap .
(by the way now it is much closer to system diagnostic results thogh i thought it will be much faster and i was wrong)
but still it was only for knowing i am safe to use .net C#
and to trust microsoft for knowing much better than me (: how to make a good proggraming language.
this is the code i was using to make it through all those dllllls import. i should have known that import anything and it costs( here it might be the import tax that is costely)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void But_StartPinvoke_Click(object sender, EventArgs e)
{
var userInputOK = TBX_SelectedProcessName.userInput();
if(!userInputOK)
MessageBox.Show(RApss.mesgs.EmptyTbx);
RApss.Strings.UserInput = TBX_SelectedProcessName.Text;
RApss.Globs.TbxPname = TBX_SelectedProcessName.Text.AddSufixEXE();
doWarmUp();
Stopwatch SwGpbn = Stopwatch.StartNew();
SwGpbn.Start();
//string _netProcName = Process.GetProcessesByName(RApss.Strings.UserInput)[0].ProcessName;
Process p = Process.GetProcessesByName(RApss.Strings.UserInput)[0];
if (p.ProcessName.ResultFetched())
SwGpbn.Stop();
var msElps_Net4 = SwGpbn.ElapsedMilliseconds;
SwGpbn.Reset();
SwGpbn.Start();
EnumProcessesV3.GetProcessByName();
SwGpbn.Stop();
var msElpsNat = SwGpbn.ElapsedMilliseconds;
SwGpbn.Reset();
SwGpbn.Reset();
if (RApss.Globs.Result.ResultFetched()) MessageBox.Show(string.Concat(RApss.Globs.Result, "\r\nWas Fetched In: ", msElpsNat, " Via PinVoke\r\n Was Fetched In: ", msElps_Net4," Via C#.NET !" ));
}
private void doWarmUp()
{
List<string> swarm = new List<string>();
for (int i = 0; i < 50000; i++)
{
swarm.Add((i + 1 *500).ToString());
}
}
}
public class RApss
{
public class Globs
{
public static string TbxPname;
public static string Result = string.Empty;
}
public class Strings
{
public static string intputForProcessName = "Requiered Process Name";
public static string UserInput = string.Empty;
}
public class mesgs
{
public static string EmptyTbx = string.Concat("please fill ", Strings.intputForProcessName, " field");
}
}
public class EnumProcessesV3
{
#region APIS
[DllImport("psapi")]
private static extern bool EnumProcesses(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] [In][Out] IntPtr[] processIds,
UInt32 arraySizeBytes,
[MarshalAs(UnmanagedType.U4)] out UInt32 bytesCopied);
[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, IntPtr dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("psapi.dll")]
static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName, [In] [MarshalAs(UnmanagedType.U4)] int nSize);
[DllImport("psapi.dll", SetLastError = true)]
public static extern bool EnumProcessModules(IntPtr hProcess,
[Out] IntPtr lphModule,
uint cb,
[MarshalAs(UnmanagedType.U4)] out uint lpcbNeeded);
[DllImport("psapi.dll")]
static extern uint GetModuleBaseName(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName, [In] [MarshalAs(UnmanagedType.U4)] int nSize);
#endregion
#region ENUMS
[Flags]
enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
#endregion
public static void GetProcessByName()
{
UInt32 arraySize = 120;
UInt32 arrayBytesSize = arraySize * sizeof(UInt32);
IntPtr[] processIds = new IntPtr[arraySize];
UInt32 bytesCopied;
bool success = EnumProcesses(processIds, arrayBytesSize, out bytesCopied);
#region <<=========== some cleanUps ============>>
// trying to check what could have been taking extra mssssnds
//Console.WriteLine("success={0}", success);
//Console.WriteLine("bytesCopied={0}", bytesCopied);
//if (!success)
//{
// MessageBox.Show("Boo!");
// return;
//}
//if (0 == bytesCopied)
//{
// MessageBox.Show("Nobody home!");
// return;
//}
#endregion
UInt32 numIdsCopied = bytesCopied >> 2;
#region <<===========same here commenting anything that might cost nerowing the options ============>>
//if (0 != (bytesCopied & 3))
//{
// UInt32 partialDwordBytes = bytesCopied & 3;
// MessageBox.Show(String.Format("EnumProcesses copied {0} and {1}/4th DWORDS... Please ask it for the other {2}/4th DWORD",
// numIdsCopied, partialDwordBytes, 4 - partialDwordBytes));
// return;
//}
//taking initialisation of SB out of loop was a winning thought but nada no change maybe in nanos
#endregion
for (UInt32 index = numIdsCopied; index> 1 ; index--) // reversing from last process id(chitting) to erlier process id did not help to win the contest
{
StringBuilder szProcessName = new StringBuilder(1000);
int x = szProcessName.Capacity;
string sName = PrintProcessName(processIds[index-1],szProcessName,x);
if (sName.Equals(RApss.Globs.TbxPname)) // tryng hardcoded value instead of reading from a variable.(GlobalsClass)
{
RApss.Globs.Result = sName;
break;
}
////////IntPtr PID = processIds[index];
////////Console.WriteLine("Name '" + sName + "' PID '" + PID + "'");
}
}
static string PrintProcessName(IntPtr processID, StringBuilder sb, int Cpcty)
{
string sName = "";
//bool bFound = false;
IntPtr hProcess = OpenProcess(ProcessAccessFlags.QueryInformation | ProcessAccessFlags.VMRead, false, processID);
if (hProcess != IntPtr.Zero)
{
IntPtr hMod = IntPtr.Zero;
uint cbNeeded = 0;
EnumProcessModules(hProcess, hMod, (uint)Marshal.SizeOf(typeof(IntPtr)), out cbNeeded);
if (GetModuleBaseName(hProcess, hMod, sb, Cpcty) > 0)
{
sName = sb.ToString();
//bFound = true;
}
// Close the process handle
CloseHandle(hProcess);
}
//if (!bFound)
//{
// sName = "<unknown>";
//}
return sName;
}
}
}
namespace RExt
{
public static class UserInputs
{
public static bool userInput(this TextBox tbxId)
{
return tbxId.Text.Length > 1;
}
}
public static class strExt
{
public static bool ResultFetched(this string StrToCheck)
{
return !string.IsNullOrWhiteSpace(StrToCheck);
}
public static string AddSufixEXE(this string StrToAppendEXE)
{
return string.Concat(StrToAppendEXE, ".exe");
}
}
}
if thats not working, make sure the project is targeting x86 CPU and Rebuild
for some reason i did not check what is needed to make it suit both x64 & x86
From a c# program I want to use WM_COPYDATA with SendMessage to communicate with a legacy c++/cli MFC application.
I want to pass a managed struct containing string objects.
I can find the handle to the c++ application for use with SendMessage fine.
The bit I don't know about is how the struct and it's strings can be marshalled and read at the other end. Especially as it contains non-blittables.
Do people think this is feasible?
I'll continue to work on it, but would apprecite someone who's done this sort of thing telling me if it just isn't going to work.
Here is some demo code if it was a c++/cli program and it's not difficult to get it working.
However, I'd like this to be in a .Net class library so it can easily be re-used.
//Quick demonstation code only, not correctly styled
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
struct MessageInfo
{
int nVersion;
char szTest[ 10 ];
};
MessageInfo sMessageInfo;
sMessageInfo.nVersion = 100;
strcpy( sMessageInfo.szTest, "TEST");
COPYDATASTRUCT CDS;
CDS.dwData = 1; //just for test
CDS.cbData = sizeof( sMessageInfo );
CDS.lpData = &sMessageInfo;
//find running processes and send them a message
//can't just search for "MYAPP.exe" as will be called "MYAPP.exe *32" on a 64bit machine
array<System::Diagnostics::Process^>^allProcesses = System::Diagnostics::Process::GetProcesses();
for each (System::Diagnostics::Process^ targetProcess in allProcesses)
{
if (targetProcess->ProcessName->StartsWith("MYAPP", System::StringComparison::OrdinalIgnoreCase))
{
HWND handle = static_cast<HWND>(targetProcess->MainWindowHandle.ToPointer());
BOOL bReturnValue = SendMessage( handle, WM_COPYDATA, (WPARAM)0, (LPARAM)&CDS ) == TRUE;
}
}
return 0;
}
I have it working.
A simple approach is to serialize the struct to a single string and transfer a string.
The swhistlesoft blog was helpful http://www.swhistlesoft.com/blog/2011/11/19/1636-wm_copydata-with-net-and-c
This may be enough to provide the simple messaging.
The struct can be re-constructed at the other end if necessary.
If a struct with any number of strings is to be marshalled as-is then it must be a fixed size, that's the main thing I wasn't getting.
The
MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9)
basically sets the size to match the c++ size which in our case is a TCHAR szTest[ 9 ];
In order to transfer a .Net struct via WM_COPYDATA from c# to c++(/cli) I had to do as follows:
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern bool SetForegroundWindow(IntPtr hWnd);
public static uint WM_COPYDATA = 74;
//from swhistlesoft
public static IntPtr IntPtrAlloc<T>(T param)
{
IntPtr retval = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(param));
System.Runtime.InteropServices.Marshal.StructureToPtr(param, retval, false);
return (retval);
}
//from swhistlesoft
public static void IntPtrFree(IntPtr preAllocated)
{
if (IntPtr.Zero == preAllocated) throw (new Exception("Go Home"));
System.Runtime.InteropServices.Marshal.FreeHGlobal(preAllocated);
preAllocated = IntPtr.Zero;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public uint dwData;
public int cbData;
public IntPtr lpData;
}
/// <summary>
/// Dot net version of AppInfo structure. Any changes to the structure needs reflecting here.
/// struct must be a fixed size for marshalling to work, hence the SizeConst entries
/// </summary>
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
struct AppInfoDotNet
{
public int nVersion;
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9)]
public string test;
};
To send a string:
COPYDATASTRUCT cd = new COPYDATASTRUCT();
cd.dwData = 2;
cd.cbData = parameters.Length + 1;
cd.lpData = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(parameters);
IntPtr cdBuffer = IntPtrAlloc(cd);
messageReceived = ((int)SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, cdBuffer)) != 0;
To receive string in C++:
else if(pCDS->dwData == 2)
{
//copydata message
CString csMessage = (LPCTSTR)pCDS->lpData;
OutputDebugString("Copydata message received: " + csMessage);
}
To send struct:
AppInfoDotNet appInfo = new AppInfoDotNet();
appInfo.test = "a test";
COPYDATASTRUCT cds3;
cds3.dwData = 1;
cds3.cbData = System.Runtime.InteropServices.Marshal.SizeOf(appInfo);
IntPtr structPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(System.Runtime.InteropServices.Marshal.SizeOf(appInfo));
System.Runtime.InteropServices.Marshal.StructureToPtr(appInfo, structPtr, false);
cds3.lpData = structPtr;
IntPtr iPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(System.Runtime.InteropServices.Marshal.SizeOf(cds3));
System.Runtime.InteropServices.Marshal.StructureToPtr(cds3, iPtr, false);
messageReceived = ((int)SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, iPtr)) != 0;
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(iPtr);
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(structPtr);
To receive struct in C++:
LRESULT CMainFrame::OnCopyData( WPARAM wParam, LPARAM lParam )
{
LRESULT lResult = FALSE;
COPYDATASTRUCT *pCDS = (COPYDATASTRUCT*)lParam;
//Matching message type for struct
if(pCDS->dwData == 1)
{
AppInfo *pAppInfo = (AppInfo*)pCDS->lpData
lResult = true;
}
Please note this is demo code and needs work in terms of styling, exception handling etc, etc...
From the documentation:
The data being passed must not contain pointers or other references to objects not accessible to the application receiving the data.
So you need to pack your string into COPYDATASTRUCT.lpData. If you have a max length for each string then you can embed it in a fixed length structure
typedef struct tagMYDATA
{
char s1[80];
char s2[120];
} MYDATA;
If you have only one variable length string you can put the string at the end and use a header followed by string data
typedef struct tagMYDATA
{
int value1;
float value2;
int stringLen;
} MYDATAHEADER;
MyCDS.cbData = sizeof(MYDATAHEADER)+(int)stringData.size();
MyCDS.lpData = new BYTE[MyCDS.cbData];
memcpy(MyCDS.lpData,&dataHeader,sizeof*(MYDATAHEADER);
StringCbCopyA (
((BYTE*)MyCDS.lpData)+sizeof*(MYDATAHEADER)
,stringData.size()
,stringData.c_str());
If you have multiple variable length strings you can still use a header and allocate more spaces for every strings plus a double null terminator, or serialize everything into one XML string.
I have a progaram that can be ran both as a winform, or from command line. If it is invoked from a command line I call AttachConsole(-1) to attach to parent console.
However, after my program ends, the user must hit enter to get back the standard command prompt ("c:\>"). is there a way to avoid that need?
Thanks.
I could wrap it in a cmd file to avoid that issue, but I would like to do it from my exe.
Try adding this line just before your exe exits...
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
Bit of a hack, but best I could find when I encountered that problem.
Here is the safest hack that solves the Enter key problem regardless of whether the console window is in the foreground, background, or minimized. You can even run it in multiple console windows.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace WindowsAndConsoleApp
{
static class Program
{
const uint WM_CHAR = 0x0102;
const int VK_ENTER = 0x0D;
[DllImport("kernel32.dll")]
static extern bool AttachConsole(int dwProcessId);
private const int ATTACH_PARENT_PROCESS = -1;
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
// Do this first.
AttachConsole(ATTACH_PARENT_PROCESS);
Console.Title = "Console Window - Enter Key Test";
Console.WriteLine("Getting the handle of the currently executing console window...");
IntPtr cw = GetConsoleWindow();
Console.WriteLine($"Console handle: {cw.ToInt32()}");
Console.WriteLine("\nPut some windows in from of this one...");
Thread.Sleep(5000);
Console.WriteLine("Take your time...");
Thread.Sleep(5000);
Console.WriteLine("Sending the Enter key now...");
// Send the Enter key to the console window no matter where it is.
SendMessage(cw, WM_CHAR, (IntPtr)VK_ENTER, IntPtr.Zero);
// Do this last.
FreeConsole();
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
}
Rob L's approach is somewhat dangerous as it will send an Enter to the active window. A better approach is to actual send the Enter to the correct process (console).
here is how
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32", SetLastError = true)]
internal static extern bool AttachConsole(int dwProcessId);
[DllImport("user32.dll")]
internal static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
internal static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
internal const int VK_RETURN = 0x0D;
internal const int WM_KEYDOWN = 0x100;
}
--snip--
bool attached = false;
// Get uppermost window process
IntPtr ptr = NativeMethods.GetForegroundWindow();
int u;
NativeMethods.GetWindowThreadProcessId(ptr, out u);
Process process = Process.GetProcessById(u);
if (string.Compare(process.ProcessName, "cmd", StringComparison.InvariantCultureIgnoreCase) == 0)
{
// attach to the current active console
NativeMethods.AttachConsole(process.Id);
attached = true;
}
else
{
// create new console
NativeMethods.AllocConsole();
}
Console.Write("your output");
NativeMethods.FreeConsole();
if (attached)
{
var hWnd = process.MainWindowHandle;
NativeMethods.PostMessage(hWnd, NativeMethods.WM_KEYDOWN, NativeMethods.VK_RETURN, 0);
}
This solution is build upon the code that is found here:
http://www.jankowskimichal.pl/en/2011/12/wpf-hybrid-application-with-parameters/
It's late to the party and there have been many suggestions over the years, but as I recently just solved this issue myself by stitching together a bunch of information from various posts, I thought I'd post the solution here since it has the most relevant title.
This solution works without using the Enter key or simulating a key press. The only thing I couldn't completely solve is intercepting the Enter from the parent console when your application starts. I think this is impossible because it happens before you get a chance to intercept it; however, there is a reasonable quasi-workaround.
Before diving into the code, here's the sequence of things we need to do:
Attach to the parent console
Capture the text of the current prompt output by the parent console
Clear the parent console's prompt by overwriting it with spaces (not sure it's possible to otherwise prevent this from happening)
Interact with the console as normal
Restore parent console's previous prompt by writing what we captured in #2
This is what it would look like in use:
using System;
using System.Windows.Forms;
public static void Main(string[] args)
{
if (args.Length > 0)
{
using (new ConsoleScope())
{
Console.WriteLine("I now own the console");
Console.WriteLine("MUA HA HA HA HA HA!!!");
}
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
... and now for the code. It's more than I'd like, but this is as succinct as I could make it for a post. May this help others attempting the same thing. Enjoy!
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
public sealed class ConsoleScope : IDisposable
{
const int ATTACH_PARENT_PROCESS = -1;
const int STD_OUTPUT_HANDLE = -11;
readonly bool createdNewConsole;
readonly string prompt;
bool disposed;
public ConsoleScope()
{
if (AttachParentConsole())
{
prompt = CaptureParentConsoleCurrentPrompt();
}
else
{
AllocConsole();
createdNewConsole = true;
}
}
~ConsoleScope() => CleanUp();
public void Dispose()
{
CleanUp();
GC.SuppressFinalize(this);
}
static string CaptureParentConsoleCurrentPrompt()
{
var line = (short)Console.CursorTop;
var length = (short)Console.CursorLeft;
var noPrompt = line == 0 && length == 0;
if (noPrompt)
{
return default;
}
return ReadCurrentLineFromParentConsoleBuffer(line, length);
}
static string ReadCurrentLineFromParentConsoleBuffer(short line, short length)
{
var itemSize = Marshal.SizeOf(typeof(CHAR_INFO));
var buffer = Marshal.AllocHGlobal(length * itemSize);
var encoding = Console.OutputEncoding;
var text = new StringBuilder(capacity: length + 1);
var coordinates = default(COORD);
var textRegion = new SMALL_RECT
{
Left = 0,
Top = line,
Right = (short)(length - 1),
Bottom = line,
};
var bufferSize = new COORD
{
X = length,
Y = 1,
};
try
{
if (!ReadConsoleOutput(GetStdOutputHandle(), buffer, bufferSize, coordinates, ref textRegion))
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
var array = buffer;
for (var i = 0; i < length; i++)
{
var info = Marshal.PtrToStructure<CHAR_INFO>(array);
var chars = encoding.GetChars(info.CharData);
text.Append(chars[0]);
array += itemSize;
}
}
finally
{
Marshal.FreeHGlobal(buffer);
}
// now that we've captured the current prompt, overwrite it with spaces
// so that things start where the parent left off at
Console.SetCursorPosition(0, line);
Console.Write(new string(' ', length));
Console.SetCursorPosition(0, line - 1);
return text.ToString();
}
void CleanUp()
{
if (disposed)
{
return;
}
disposed = true;
RestoreParentConsolePrompt();
if (createdNewConsole)
{
FreeConsole();
}
}
void RestoreParentConsolePrompt()
{
var text = prompt;
if (!string.IsNullOrEmpty(text))
{
// this assumes the last output from your application used
// Console.WriteLine or otherwise output a CRLF. if it didn't,
// you may need to add an extra line here
Console.Write(text);
}
}
[StructLayout(LayoutKind.Sequential)]
struct CHAR_INFO
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public byte[] CharData;
public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
struct COORD
{
public short X;
public short Y;
}
[StructLayout(LayoutKind.Sequential)]
struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
// REF: https://learn.microsoft.com/en-us/windows/console/allocconsole
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AllocConsole();
// REF: https://learn.microsoft.com/en-us/windows/console/attachconsole
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(int dwProcessId);
// REF: https://learn.microsoft.com/en-us/windows/console/freeconsole
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FreeConsole();
static bool AttachParentConsole() => AttachConsole(ATTACH_PARENT_PROCESS);
// REF: https://learn.microsoft.com/en-us/windows/console/readconsoleoutput
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadConsoleOutput(IntPtr hConsoleOutput, IntPtr lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, ref SMALL_RECT lpReadRegion);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
static IntPtr GetStdOutputHandle() => GetStdHandle(STD_OUTPUT_HANDLE);
}
Ok, I don't have the solution, but it seems to be because the cmd.exe is not waiting on the started process, whereas with a normal console application cmd.exe waits until the the application exits. I don't know what makes cmd.exe decide to wait or not on an application, normal Windows Forms applications are just started and cmd.exe doesn't wait for it to exit. Maybe this hint triggers somebody! I will dig a bit deeper in the mean while.
Try calling the FreeConsole function prior to exiting your executable.
This one has been the easiest solution for me:
myapp.exe [params] | ECHO.
I attempted my own Qt cpp version of Chris Martinez's C# answer:
https://github.com/NightVsKnight/QtGuiConsoleApp/blob/main/QtGuiConsoleApp/main.cpp
#include <QApplication>
#include <QMessageBox>
#ifdef Q_OS_WIN
// Solution posted to https://stackoverflow.com/a/73942013/252308
#define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
QString consolePromptClear()
{
QString prompt = nullptr;
auto bSuccess = AttachConsole(ATTACH_PARENT_PROCESS);
if (bSuccess)
{
auto hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdOut != INVALID_HANDLE_VALUE)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
bSuccess = GetConsoleScreenBufferInfo(hStdOut, &csbi);
if (bSuccess)
{
auto dwConsoleColumnWidth = (DWORD)(csbi.srWindow.Right - csbi.srWindow.Left + 1);
auto xEnd = csbi.dwCursorPosition.X;
auto yEnd = csbi.dwCursorPosition.Y;
if (xEnd != 0 || yEnd != 0)
{
DWORD dwNumberOfChars;
SHORT yBegin = yEnd;
{
// Walk backwards to find first all blank line
auto pBuffer = (LPWSTR)LocalAlloc(LPTR, dwConsoleColumnWidth * sizeof(WCHAR));
while (yBegin)
{
COORD dwReadCoord = { 0, yBegin };
bSuccess = ReadConsoleOutputCharacterW(hStdOut, pBuffer, dwConsoleColumnWidth, dwReadCoord, &dwNumberOfChars);
if (!bSuccess) break;
DWORD i;
for (i=0; i < dwNumberOfChars; ++i)
{
WCHAR wchar = pBuffer[i];
if (wchar != L' ')
{
--yBegin;
break;
}
}
if (i == dwNumberOfChars)
{
// Found all blank line; we want the *next* [non-blank] line
yBegin++;
break;
}
}
LocalFree(pBuffer);
}
auto promptLength = (yEnd - yBegin) * dwConsoleColumnWidth + xEnd;
auto lpPromptBuffer = (LPWSTR)LocalAlloc(LPTR, promptLength * sizeof(WCHAR));
COORD dwPromptCoord = { 0, yBegin };
bSuccess = ReadConsoleOutputCharacterW(hStdOut, lpPromptBuffer, promptLength, dwPromptCoord, &dwNumberOfChars);
if (bSuccess)
{
Q_ASSERT(promptLength == dwNumberOfChars);
prompt = QString::fromWCharArray(lpPromptBuffer, dwNumberOfChars);
bSuccess = SetConsoleCursorPosition(hStdOut, dwPromptCoord);
if (bSuccess)
{
FillConsoleOutputCharacterW(hStdOut, L' ', promptLength, dwPromptCoord, &dwNumberOfChars);
}
}
LocalFree(lpPromptBuffer);
}
}
}
}
if (prompt.isEmpty())
{
FreeConsole();
return nullptr;
}
else
{
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
freopen_s((FILE**)stderr, "CONOUT$", "w", stderr);
freopen_s((FILE**)stdin, "CONIN$", "r", stdin);
return prompt;
}
}
void consolePromptRestore(const QString& prompt)
{
if (prompt.isEmpty()) return;
auto hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdOut == INVALID_HANDLE_VALUE) return;
CONSOLE_SCREEN_BUFFER_INFO csbi;
BOOL bSuccess = GetConsoleScreenBufferInfo(hStdOut, &csbi);
if (!bSuccess) return;
auto xEnd = csbi.dwCursorPosition.X;
auto yEnd = csbi.dwCursorPosition.Y;
if (xEnd == 0 && yEnd == 0) return;
auto buffer = prompt.toStdWString();
auto lpBuffer = buffer.data();
auto nLength = (DWORD)buffer.length();
COORD dwWriteCoord = { 0, (SHORT)(yEnd + 1) };
DWORD dwNumberOfCharsWritten;
WriteConsoleOutputCharacterW(hStdOut, lpBuffer, nLength, dwWriteCoord, &dwNumberOfCharsWritten);
dwWriteCoord = { (SHORT)dwNumberOfCharsWritten, (SHORT)(yEnd + 1) };
SetConsoleCursorPosition(hStdOut, dwWriteCoord);
}
#else
// Non-Windows impl...
#endif
int main(int argc, char *argv[])
{
// NOTE: Any console output before call to consolePromptClear() may get cleared.
// NOTE: Console vs GUI mode has **NOTHING** to do with being passed arguments; You can easily pass arguments to GUI apps.
int returnCode;
auto prompt = consolePromptClear();
if (prompt.isEmpty())
{
QApplication a(argc, argv);
a.setQuitOnLastWindowClosed(true);
QMessageBox msgBox(nullptr);
msgBox.setWindowTitle(a.applicationName());
msgBox.setTextFormat(Qt::RichText);
msgBox.setText("App is detected to be running as a GUI");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.show();
returnCode = a.exec();
}
else
{
QCoreApplication a(argc, argv);
QTextStream qout(stdout);
qout << "App is detected to be running as a Console" << Qt::endl;
returnCode = 0;
consolePromptRestore(prompt);
}
return returnCode;
}
I want to get the hardwired serial number from the hard disk but NOT using WMI. I tried using WMI code, and it doesn't work on my machine for sure. So is there any alternative in .NET for finding the Serial Number of a physical hard disk?
This should help get started:
How to get Physical HDD serial number without WMI
Regarding your problem with WMI not returning data; Are you sure how to the right privileges to get data from WMI? You can use WMI Tools to check/fix this.
Use the createfile as shown below. It may require administrative permissions.Add 4 text boxes and a button in your code.
private const int CREATE_NEW = 1;
private const int OPEN_EXISTING = 3;
private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
// 10000000000000000000000000000000 --- GENERIC_READ
// 01000000000000000000000000000000 --- GENERIC_WRITE
// 00100000000000000000000000000000 --- GENERIC_EXECUTE
// 00010000000000000000000000000000 --- GENERIC_ALL
//Securable objects use an access mask format in which the
//four high-order bits specify generic access rights
private const int FILE_SHARE_READ = 0x1;
private const int FILE_SHARE_WRITE = 0x2;
// 00000000000000000000000000000001 --- FILE_SHARE_READ
// 00000000000000000000000000000010 --- FILE_SHARE_WRITE
// 00000000000000000000000000000100 --- FILE_SHARE_DELETE
private const int VER_PLATFORM_WIN32_NT = 2;
private const int DFP_RECEIVE_DRIVE_DATA = 0x7C088;
// 0 000000000000111 11 0 00000100010 00
// Common Device Type Required Access Custom Function Code Transfer Type
private const int INVALID_HANDLE_VALUE = -1;
public enum DriveTypes { Fixed, Removable, Unknown };
public string[] DriveStrings = { "Fixed", "Removable", "Unknown" };
[StructLayout(LayoutKind.Sequential, Size = 8)]
private class IDEREGS
{
public byte Features;
public byte SectorCount;
public byte SectorNumber;
public byte CylinderLow;
public byte CylinderHigh;
public byte DriveHead;
public byte Command;
public byte Reserved;
}
[StructLayout(LayoutKind.Sequential, Size = 32)]
private class SENDCMDINPARAMS
{
public int BufferSize;
public IDEREGS DriveRegs;
public byte DriveNumber;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] Reserved;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] Reserved2;
public SENDCMDINPARAMS()
{
DriveRegs = new IDEREGS();
Reserved = new byte[3];
Reserved2 = new int[4];
}
}
[StructLayout(LayoutKind.Sequential, Size = 12)]
private class DRIVERSTATUS
{
public byte DriveError;
public byte IDEStatus;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public byte[] Reserved;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public int[] Reserved2;
public DRIVERSTATUS()
{
Reserved = new byte[2];
Reserved2 = new int[2];
}
}
[StructLayout(LayoutKind.Sequential)]
private class IDSECTOR
{
public short GenConfig;
public short NumberCylinders;
public short Reserved;
public short NumberHeads;
public short BytesPerTrack;
public short BytesPerSector;
public short SectorsPerTrack;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public short[] VendorUnique;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public char[] SerialNumber;
public short BufferClass;
public short BufferSize;
public short ECCSize;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public char[] FirmwareRevision;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
public char[] ModelNumber;
public short MoreVendorUnique;
public short DoubleWordIO;
public short Capabilities;
public short Reserved1;
public short PIOTiming;
public short DMATiming;
public short BS;
public short NumberCurrentCyls;
public short NumberCurrentHeads;
public short NumberCurrentSectorsPerTrack;
public int CurrentSectorCapacity;
public short MultipleSectorCapacity;
public short MultipleSectorStuff;
public int TotalAddressableSectors;
public short SingleWordDMA;
public short MultiWordDMA;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 382)]
public byte[] Reserved2;
public IDSECTOR()
{
VendorUnique = new short[3];
Reserved2 = new byte[382];
FirmwareRevision = new char[8];
SerialNumber = new char[20];
ModelNumber = new char[40];
}
}
[StructLayout(LayoutKind.Sequential)]
private class SENDCMDOUTPARAMS
{
public int BufferSize;
public DRIVERSTATUS Status;
public IDSECTOR IDS;
public SENDCMDOUTPARAMS()
{
Status = new DRIVERSTATUS();
IDS = new IDSECTOR();
}
}
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern int CloseHandle(int hObject);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern int CreateFile(
string lpFileName,
uint dwDesiredAccess,
int dwShareMode,
int lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
int hTemplateFile
);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern int DeviceIoControl(
int hDevice,
int dwIoControlCode,
[In(), Out()] SENDCMDINPARAMS lpInBuffer,
int lpInBufferSize,
[In(), Out()] SENDCMDOUTPARAMS lpOutBuffer,
int lpOutBufferSize,
ref int lpBytesReturned,
int lpOverlapped
);
private string SwapChars(char[] chars)
{
for (int i = 0; i <= chars.Length - 2; i += 2)
{
char t;
t = chars[i];
chars[i] = chars[i + 1];
chars[i + 1] = t;
}
string s = new string(chars);
return s;
}
private void button1_Click(object sender, System.EventArgs e)
{
string serialNumber = " ", model = " ", firmware = " ";
bool result;
DriveTypes driveType = DriveTypes.Unknown;
int handle, returnSize = 0;
int driveNumber = 0;
SENDCMDINPARAMS sci = new SENDCMDINPARAMS();
SENDCMDOUTPARAMS sco = new SENDCMDOUTPARAMS();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
// \\.\PhysicalDrive0 Opens the first physical drive.
// \\.\PhysicalDrive2 Opens the third physical drive.
// see more info on http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
handle = CreateFile("\\\\.\\PhysicalDrive" + "0", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
else // for win'9x
handle = CreateFile("\\\\.\\Smartvsd", 0, 0, 0, CREATE_NEW, 0, 0);
if (handle != INVALID_HANDLE_VALUE)
{
sci.DriveNumber = (byte)driveNumber;
sci.BufferSize = Marshal.SizeOf(sco);
sci.DriveRegs.DriveHead = (byte)(0xA0 | driveNumber << 4);
sci.DriveRegs.Command = 0xEC;
sci.DriveRegs.SectorCount = 1;
sci.DriveRegs.SectorNumber = 1;
if (DeviceIoControl(handle, DFP_RECEIVE_DRIVE_DATA, sci, Marshal.SizeOf(sci), sco, Marshal.SizeOf(sco), ref returnSize, 0) != 0)
{
serialNumber = SwapChars(sco.IDS.SerialNumber);
model = SwapChars(sco.IDS.ModelNumber);
firmware = SwapChars(sco.IDS.FirmwareRevision);
}
textBox1.Text = serialNumber;
textBox2.Text = model;
textBox3.Text = firmware;
if ((sco.IDS.GenConfig & 0x80) == 0x40)
driveType = DriveTypes.Removable;
else if ((sco.IDS.GenConfig & 0x40) == 0x40)
driveType = DriveTypes.Fixed;
else
driveType = DriveTypes.Unknown;
CloseHandle(handle);
textBox4.Text = DriveStrings[(int)driveType];
}
}
A perfectly working solution can be found here:
http://www.codeproject.com/Articles/16941/Get-Physical-HDD-Serial-Number-without-WMI
[edit]
Sorry, missed that a reference to this has already been submitted.
You can use the:
GetVolumeInformation
Win32 API function to get this information, if you must avoid WMI. The linked page gives full the declaration signature (in both VB & C#) for the API function along with sample code.
Your best options is windows api,
i made a simple search and got this
General processor info and also read this post
I am using hdd firmware track in my projects. I programmed behind the mdi form to look for hdd firmware number, then its looping on all the provided numbers of hdds of companies computers and if it matches anyone amongst these provided hdds firmware numbers, then run the applicatoin and load mdi form other wise provides a message "The application is not registed on this machine, please call Mr. Afridi to register this application on 00923339176357. My email address is munawar_s_afridi#yahoo.com. I will send complete source code of fetching how to programm professionaly hdd firmware number and how to stop others by using your app illegaly.
One lovely thing that you should specify all the company computers hdd firmware at once and let the application first pick the hdd firmware number, store it in a variable and then pick the value of this variable(a string value) and loop it one by one with each hdd number by using OR logical operator. If it finds a matching number amongst the companies hdds numbers with variable value, then app should load main form(mdi) other wise notify the user for registration.
The sample code will be provided using vb6. You can easily programm later on in vb.net just by understanding how to call unmanaged code in to a managed .net application. In this case a .dll.