I'm trying to eject a USB drive in C# after I've copied some files to it.
However, after reading a lot of the examples of how to do that, I can't get anything to work for me.
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr CreateFile
(string filename, uint desiredAccess,
uint shareMode, IntPtr securityAttributes,
int creationDisposition, int flagsAndAttributes,
IntPtr templateFile);
How I'm calling it:
string path = "\\\\.\\" + driveLetter + ":";
IntPtr handle = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0,
IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
MessageBox.Show(GetLastError().ToString());//gives me zero
My drive letter is K so it just puts that in the string and attempts to open the file (I'm not really sure about that), I'm hoping it will be the drive so that I can eject the USB thumbdrive.
Problem is that handle is always -1
Am I formatting the path string wrong? Or am I using the CreateFile method incorrectly to get a handle to the drive I want to eject?
related:
Eject USB device via C#
Safely remove a USB drive using the Win32 API? (and related links)
Found the answer here (Eject USB device via C#, see Roger Deep's answer)
Short answer:
IntPtr handle = CreateFile(path, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, 0x3, 0,
IntPtr.Zero);
I have no idea what the difference is, but it gives me the correct handle to the drive and the rest of the code now works!
Long answer, my complete code to remove a USB drive (WPF Window):
private void Button_Click_1(object sender, RoutedEventArgs e)
{
EjectDrive('K');
}
void EjectDrive(char driveLetter)
{
string path = #"\\.\" + driveLetter + #":";
IntPtr handle = CreateFile(path, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, 0x3, 0, IntPtr.Zero);
if ((long)handle == -1)
{
MessageBox.Show("Unable to open drive " + driveLetter);
return;
}
int dummy = 0;
DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0,
IntPtr.Zero, 0, ref dummy, IntPtr.Zero);
CloseHandle(handle);
MessageBox.Show("OK to remove drive.");
}
[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr CreateFile
(string filename, uint desiredAccess,
uint shareMode, IntPtr securityAttributes,
int creationDisposition, int flagsAndAttributes,
IntPtr templateFile);
[DllImport("kernel32")]
private static extern int DeviceIoControl
(IntPtr deviceHandle, uint ioControlCode,
IntPtr inBuffer, int inBufferSize,
IntPtr outBuffer, int outBufferSize,
ref int bytesReturned, IntPtr overlapped);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
Related
I want to get all tray icons on windows7(64bit) with C#,but when i used the windows api "ReadProcessMemory" ,the tray button Text can't be recognied.
codes below
IntPtr pid = IntPtr.Zero;
IntPtr ipHandle = IntPtr.Zero;
IntPtr lTextAdr = IntPtr.Zero;
IntPtr ipTray = TrayToolbarWindow32();
WinApiHelper.GetWindowThreadProcessId(ipTray, ref pid);
if (pid.Equals(0))
return iconList;
IntPtr hProcess = WinApiHelper.OpenProcess(WinApiHelper.PROCESS_ALL_ACCESS | WinApiHelper.PROCESS_VM_OPERATION | WinApiHelper.PROCESS_VM_READ | WinApiHelper.PROCESS_VM_WRITE, IntPtr.Zero, pid);
IntPtr lAddress = WinApiHelper.VirtualAllocEx(hProcess, 0, 4096, WinApiHelper.MEM_COMMIT, WinApiHelper.PAGE_READWRITE);
int lButton = WinApiHelper.SendMessage(ipTray, WinApiHelper.TB_BUTTONCOUNT, 0, 0);
for (int i = 0; i < lButton; i++)
{
WinApiHelper.SendMessage(ipTray, WinApiHelper.TB_GETBUTTON, i, lAddress);
WinApiHelper.ReadProcessMemory(hProcess, (IntPtr)(lAddress.ToInt32() + 16), ref lTextAdr, 4, 0);
if (!lTextAdr.Equals(-1))
{
byte[] buff = new byte[ 1024 ];
WinApiHelper.ReadProcessMemory(hProcess, lTextAdr, buff, 1024, 0);
string title = System.Text.ASCIIEncoding.Unicode.GetString(buff);
and api declaration
[DllImport("kernel32", EntryPoint = "ReadProcessMemory")]
public static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, ref IntPtr lpBuffer, int nSize, int lpNumberOfBytesWritten);
[DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory")]
public static extern bool ReadProcessMemoryEx(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out] byte[] buffer, IntPtr size, out IntPtr lpNumberOfBytesRead);
[DllImport("kernel32", EntryPoint = "ReadProcessMemory")]
public static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out] byte[] lpBuffer, int nSize, int lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, UIntPtr nSize, IntPtr lpNumberOfBytesRead);
the problem is here
string title = System.Text.ASCIIEncoding.Unicode.GetString(buff);
when converted , the string "title" can't be recognized,it maybe like
ǎ\0\0\0\0Д\0\0à\0\0ƿ\r\0\0\0\0\0\0\0\0\0\0D:\\Tools\\ESET Smart Security\\egui.exe\0\0\0\0\0\0\0\0\0\0\0\0\0\
i don't know why, help.
You may want to consider what you are doing. ReadProcessMemory is a debug function designed for debuggers which requires SeDebugPrivilege, so I hope you are writing a debugger. Ignoring the queasiness I get from considering use of these functions in a non-debug capacity, you are leaking the buffer you allocated and requiring that your application run as administrator.
If this application is only for your own purposes and for no-one else, then check out the related question Systray Access, as it seems that there is a separate TB_GETBUTTONTEXT message. I imagine that you are actually receiving the button data, rather than text in the copied memory, causing issues.
I am using CDB (Microsoft Console Debugger,) and WinDbg to try to force a break when heap corruption occurs by P/Invoke into ReadFile. I read many more bytes from a text file than what I've allocated to the chBuf array. The debugger does not see the access violation until after GC.Collect, which is too late for me. Prior to running my program, I run
gflags -p /enable testheap.exe /unaligned
The effect seems useless. I wrote this little test program to apply what I find to debugging a much larger commercial program that is having heap corruption issues.
I have also tried DebugDiag with Application Verifier and MDA callbackOnCollectedDelegate without success. Isn't my use of gflags supposed to detect heap corruption immediately after ReadFile?
The code:
namespace TestHeap
public partial class Form1 : Form
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadFile(SafeFileHandle hFile, [Out] byte[] lpBuffer,
uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
string fileName = "testHeap.txt";
const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING = 3;
SafeFileHandle sh;
byte[] chBuf = new byte[8];
public Form1()
{
InitializeComponent();
}
private void testBtn_Click(object sender, EventArgs e)
{
bool nStat;
uint bytesToRead = 1025;
uint bytesRead = 0;
if (!(nStat = ReadFile( sh, chBuf, bytesToRead, out bytesRead, IntPtr.Zero)))
Debug.Print("testBtn_Click error in ReadFile, nStat = {0}", nStat);
MessageBox.Show(string.Format("After ReadFile, bytesToRead = {0},\n bytes read = {1}", bytesToRead, bytesRead));
GC.Collect();
MessageBox.Show("testBtn_Click end, after GC.Collect");
}
private void Form1_Load(object sender, EventArgs e)
{
sh = CreateFile(fileName, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
}
}
}
Just a guess but I believe the unexpected gflags behaviour is caused by this line:
byte[] chBuf = new byte[8];
Since chBuf is managed by the CLR, gflags is not able to put the fill pattern after it to detect buffer overruns. Try changing that to:
IntPtr chBuf = Marshal.AllocHGlobal(8);
So that you will be allocating in the unmanaged heap. Gflags should be able to work with that. Also, you may need to change the signature of ReadFile for that to work:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadFile(SafeFileHandle hFile, [Out] IntPtr lpBuffer,
uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
This question already has answers here:
Closed 11 years ago.
Possible Duplicates:
Windows CDROM Eject
Open CD/DVD door with a Windows API call?
I have looked around and can't find a simple solution to what I want to do.
I want to open a CD-Rom from my C# app. It should check if the media is in fact a cd- rom and then open it. Is there a quick solution to this or am I missing something?
Check this URL, it has both managed and unmanaged code for .net
http://bytes.com/topic/c-sharp/answers/273513-how-eject-cd-rom-c
Try below code :
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace EjectMedia
{
class Program
{
static void Main(string[] args)
{
// My CDROM is on drive E:
EjectMedia.Eject(#"\\.\E:");
}
}
class EjectMedia
{
// Constants used in DLL methods
const uint GENERICREAD = 0x80000000;
const uint OPENEXISTING = 3;
const uint IOCTL_STORAGE_EJECT_MEDIA = 2967560;
const int INVALID_HANDLE = -1;
// File Handle
private static IntPtr fileHandle;
private static uint returnedBytes;
// Use Kernel32 via interop to access required methods
// Get a File Handle
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
uint desiredAccess,
uint shareMode,
IntPtr attributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);
[DllImport("kernel32", SetLastError=true)]
static extern int CloseHandle(IntPtr driveHandle);
[DllImport("kernel32", SetLastError = true)]
static extern bool DeviceIoControl(IntPtr driveHandle,
uint IoControlCode,
IntPtr lpInBuffer,
uint inBufferSize,
IntPtr lpOutBuffer,
uint outBufferSize,
ref uint lpBytesReturned,
IntPtr lpOverlapped);
public static void Eject(string driveLetter)
{
try
{
// Create an handle to the drive
fileHandle = CreateFile(driveLetter,
GENERICREAD,
0,
IntPtr.Zero,
OPENEXISTING,
0,
IntPtr.Zero);
if ((int)fileHandle != INVALID_HANDLE)
{
// Eject the disk
DeviceIoControl(fileHandle,
IOCTL_STORAGE_EJECT_MEDIA,
IntPtr.Zero, 0,
IntPtr.Zero, 0,
ref returnedBytes,
IntPtr.Zero);
}
}
catch
{
throw new Exception(Marshal.GetLastWin32Error().ToString());
}
finally
{
// Close Drive Handle
CloseHandle(fileHandle);
fileHandle = IntPtr.Zero;
}
}
}
}
I'm trying to send an IOCTL_SERVICE_REFRESH command to the GPS Intermediate Driver service using C# like this:
handle = CreateFile("GPD0:",
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
IntPtr.Zero);
if (handle.ToInt32() == INVALID_HANDLE_VALUE)
{
rc = Marshal.GetLastWin32Error();
return rc;
}
int numBytesReturned = 0;
rc = DeviceIoControl(
handle,
IOCTL_SERVICE_REFRESH,
null,
0,
null,
0,
ref numBytesReturned,
IntPtr.Zero);
int error = Marshal.GetLastWin32Error();
GetLastWin32Error always gives me error 21 (device not ready). However, the equivalent call when made from C++ works fine:
HANDLE hGPS = CreateFile(L"GPD0:", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hGPS != INVALID_HANDLE_VALUE) {
BOOL ret = DeviceIoControl(hGPS,IOCTL_SERVICE_REFRESH,0,0,0,0,0,0);
DWORD err = GetLastError();
}
I suspected a problem with the PInvoke signatures, but I can't seem to find anything wrong here:
[DllImport("coredll.dll", EntryPoint = "DeviceIoControl", SetLastError = true)]
public static extern int DeviceIoControl(
IntPtr hDevice,
uint dwIoControlCode,
byte[] lpInBuffer,
int nInBufferSize,
byte[] lpOutBuffer,
int nOutBufferSize,
ref int lpBytesReturned,
IntPtr lpOverlapped);
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr CreateFile(
String lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr attr,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
What am I missing here?
I am having to write an application that communicates with a third-party program (AOL, I'm sorry. :()
Doing a lot of research I found some ways to do this with P/Invoke, and for the most part it works okay, but it crashes upon subsequent trials, specifically with SendMessage. I'm outlining the crashing code below.
All of this was ported to .NET from old, old Visual Basic files. It's archaic as it can be, and I understand if it's not doable - I was just hoping there was a better way than Visual Basic 4.0 to get this done.
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent,
IntPtr hwndChildAfter,
string lpszClass,
string lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle,
IntPtr childAfter,
string className,
IntPtr windowTitle);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", EntryPoint="SendMessageW")]
public static extern IntPtr SendMessageByString(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
StringBuilder lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode , EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessageByString(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
String lParam);
public IntPtr FindClientWindow()
{
IntPtr aol = IntPtr.Zero;
IntPtr mdi = IntPtr.Zero;
IntPtr child = IntPtr.Zero;
IntPtr rich = IntPtr.Zero;
IntPtr aollist = IntPtr.Zero;
IntPtr aolicon = IntPtr.Zero;
IntPtr aolstatic = IntPtr.Zero;
aol = Invoke.FindWindow("AOL Frame25", null);
mdi = Invoke.FindWindowEx(aol, IntPtr.Zero, "MDIClient", null);
child = Invoke.FindWindowEx(mdi, IntPtr.Zero, "AOL Child", null);
rich = Invoke.FindWindowEx(child, IntPtr.Zero, "RICHCNTL", null);
aollist = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Listbox", null);
aolicon = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Icon", null);
aolstatic = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Static", null);
if (rich != IntPtr.Zero &&
aollist != IntPtr.Zero &&
aolicon != IntPtr.Zero &&
aolstatic != IntPtr.Zero)
return child;
do
{
child = Invoke.FindWindowEx(mdi, child, "AOL Child", null);
rich = Invoke.FindWindowEx(child, IntPtr.Zero, "RICHCNTL", null);
aollist = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Listbox", null);
aolicon = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Icon", null);
aolstatic = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Static", null);
if (rich != IntPtr.Zero &&
aollist != IntPtr.Zero &&
aolicon != IntPtr.Zero &&
aolstatic != IntPtr.Zero)
return child;
}
while (child != IntPtr.Zero)
;
return child;
}
IntPtr room = IntPtr.Zero;
IntPtr child = IntPtr.Zero;
IntPtr length = IntPtr.Zero;
IntPtr roomHandle = IntPtr.Zero;
child = FindClientWindow();
room = FindChildByClass(child, "RICHCNTLREADONLY");
HandleRef n = new HandleRef(IntPtr.Zero, room);
length = SendMessage(n, 0x000E, IntPtr.Zero, IntPtr.Zero);
// This is the line that keeps crashing on me.
SendMessageByString(n, 0x000D, new IntPtr( length.ToInt32() + 1 ), str);
public IntPtr FindChildByClass(IntPtr parent, string child)
{
return Invoke.FindWindowEx(parent, IntPtr.Zero, child, null);
}
You are using the Wide byte SendMessage..ie for Wide Characters, have you tried the normal Sendmessage..
public static extern int SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
I also notice it's like as if you are trying to change the value based on the handle of the richtextbox's control hence the looking around for the AOL's client window in another process...is that correct?
That could be the source of the problem, directly modifying a control that belongs to a window that is not yours (your program is managed, modifying a unmanaged process's window)...that could explain why it crashed. Can you clarify what is the hex constants for?
Edit: When you use the WM_GETTEXTLENGTH and WM_GETTEXT, they are part of the Windows Messages to retrieve the text length and the actual text from the control. If you look here and see what pinvoke.net has to say about them..When you issue a 'SendMessage', with WM_GETTEXTLENGTH and WM_GETTEXT, you are telling Windows - 'Hey, get me the length of the text in that associated handle which I've given you in the parameter n. Just occurred to me, worth trying out...I would get rid of those SendMessage pinvokes and use just this one..
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, StringBuilder lParam);
//If you use '[Out] StringBuilder', initialize the string builder with proper length first.
child = FindClientWindow();
room = FindChildByClass(child, "RICHCNTLREADONLY");
length = SendMessage(n, 0x000E, IntPtr.Zero, IntPtr.Zero);
StringBuilder sbBuf = new StringBuilder(length);
SendMessageByString(room, 0x000D, new IntPtr( length.ToInt32() + 1 ), out sbBuf); // this is the line that keeps crashing on me.
Try that and get back here ... :)
Hope this helps,
Best regards,
Tom.
Did you manage to solve the "Attempted to read or write protected memory." error? t0mm13b's answer seems to allocate a StringBuilder whose buffer is one character too small (to accommodate the trailing '\0').
Here's code that works for me:
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
private static extern Int32 SendMessageByString(IntPtr wnd, UInt32 msg, Int32 WParam, StringBuilder output);
const int WM_GETTEXTLENGTH = 0x000e;
const int WM_GETTEXT = 0x000d;
public static string GetText(IntPtr hWnd)
{
int len = SendMessageByString(hWnd, WM_GETTEXTLENGTH, 0, null);
var sb = new StringBuilder(len + 1); // +1 is for the trailing '\0'
SendMessageByString(hWnd, WM_GETTEXT, sb.Capacity, sb);
return sb.ToString();
}
I was getting a crash from Marshal.PtrToStringUni(bf) statement in similar situation where SendMessage was returning a "wrong size" for a text length with WM_GETTEXTLENGTH argument (the control class was "RICHEDIT50W"; multi-line text).
I had tried adding 1, 10, 100 (to text length query result) and still would get an error even though (later on) the text length was equal what was returned from the first call (WM_GETTEXTLENGTH).
My solution was: I multiplied the result with 2 then I trimmed it.
I did use Marshal.AllocHGlobal(sz) and then Marshal.Release(bf), so there was no problem with memory efficiency. My guess is that for multi-line texts Marshal.AllocHGlobal(sz) wasn't making enough space in the memory even with exact text size (+1).
Maybe the return character within the text (vbCr, vbLf) requires more memory: I found nothing to explain this isue, but doubling the size worked for me.