티스토리 뷰
| ||
Download the code for this article: CQA0208.exe (47 KB) | ||
Q How do I call a DLL or Win32® API function from my C# code if the function has a string (char*) output parameter? I can pass a string for an input parameter, but how do I get the returned string? Also, how do I call functions that require a struct or a callback, like GetWindowsRect and EnumWindows? I'm trying to convert from C++ and MFC, and I can't cope! E. Nguyen A One of the major benefits of Microsoft® .NET is that it provides a language-independent development system. You can write classes in Visual Basic®, C++, C#—whatever—and use them in other languages; you can even derive from classes in a different language. But what happens when you want to call some old-school unmanaged DLL? You have to somehow translate .NET objects into the structs, char*'s, and function pointers C expects. In techno lingo, your parameters must be marshaled. Marshaling is a big topic, but luckily you don't have to know much to get the job done. ![]() using System.Runtime.InteropServices; // DllImport public class Win32 { [DllImport("User32.Dll")] public static extern void SetWindowText(int h, String s); } ![]() csc /t:library /out:Win32API.dll Win32API.csNow you have a Win32API.dll you can use in any C# project. using Win32API; int hwnd = // get it... String s = "I'm so cute." Win32.SetWindowText(hwnd, s);The compiler knows to find SetWindowText in user32.dll and automatically converts your string to LPTSTR (TCHAR*) before calling. Amazing! How does .NET perform this magic? Every C# type has a default marshaling type. For strings, it's LPTSTR. But what happens if you try this for GetWindowText, where the string is an out parameter, not an in parameter? It doesn't work because strings are immutable. Really. You may not have noticed, but whenever you do something to a string, a new string is created. To modify the one and only actual string, you have to use StringBuilder: using System.Text; // for StringBuilder public class Win32 { [DllImport("user32.dll")] public static extern int GetWindowText(int hwnd, StringBuilder buf, int nMaxCount); }The default marshaling type for StringBuilder is also LPTSTR, but now GetWindowText can modify your actual string. int hwnd = // get it... StringBuilder cb = new StringBuilder(256); Win32.GetWindowText(hwnd, sb, sb.Capacity); ![]() ![]() [DllImport("user32.dll")] public static extern int GetClassName(int hwnd, [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf, int nMaxCount);Now when you call GetClassName, .NET passes your string as an ANSI char, not a wide one. Whew! ![]() // in C/C++ RECT rc; HWND hwnd = FindWindow("foo",NULL); ::GetWindowRect(hwnd, &rc); ![]() [StructLayout(LayoutKind.Sequential)] public struct RECT { public int left; public int top; public int right; public int bottom; }Once you have your struct defined, you can implement the wrapper like so: [DllImport("user32.dll")] public static extern int GetWindowRect(int hwnd, ref RECT rc);It's important to use ref so the CLR will pass your RECT as a reference so the function can modify your object, not a nameless stack copy. With GetWindowRect defined, you can call it like so: RECT rc = new RECT(); int hwnd = // get it ... Win32.GetWindowRect(hwnd, ref rc);Note that you must use the ref in the call as well as the declaration—picky! The default marshaling type for C# structs is—what else?—LPStruct, so there's no need for MarshalAs. But if you made RECT a class instead of a struct, you'd have to implement the wrapper like this: // if RECT is a class, not struct [DllImport("user32.dll")] public static extern int GetWindowRect(int hwnd, [MarshalAs(UnmanagedType.LPStruct)] RECT rc); ![]() [DllImport("user32.dll")] public static extern int GetWindowRect(int hwnd, ref Rectangle rc);The runtime already knows how to marshal a Rectangle as a Win32 RECT. [Editor's Update - 12/9/2005: The runtime does not have built-in support for marshaling a Rectangle as a RECT, and thus the previous code snippet will not work as described.] Please note, however, that there's no need to call GetWindowRect (or Get/SetWindowText for that matter) in real code, since the Windows.Forms Control class has properties for this: Control.DisplayRectangle to get the window rectangle, and Control.Text to get or set the text. Rectangle r = mywnd.DisplayRectangle; mywnd.Text = "I'm so cute"; ![]() ![]() delegate bool EnumWindowsCB(int hwnd, int lparam);Once you've declared your delegate/callback type, the wrapper goes like this: [DllImport("user32")] public static extern int EnumWindows(EnumWindowsCB cb, int lparam);Since the delegate line merely declares a delegate type, you have to provide an actual delegate in your class // in your class public static bool MyEWP(int hwnd, int lparam) { // do something return true; }then pass it to the wrapper like so: EnumWindowsCB cb = new EnumWindowsCB(MyEWP); Win32.EnumWindows(cb, 0); ![]() // lparam is IntPtr now delegate bool EnumWindowsCB(int hwnd, IntPtr lparam); // wrap object in GCHandle MyClass obj = new MyClass(); GCHandle gch = GCHandle.Alloc(obj); EnumWindowsCB cb = new EnumWindowsCB(MyEWP); Win32.EnumWindows(cb, (IntPtr)gch); gch.Free(); ![]() public static bool MyEWP(int hwnd, IntPtr param) { GCHandle gch = (GCHandle)param; MyClass c = (MyClass)gch.Target; // ... use it return true; } ![]() WindowArray wins = new WindowArray(); foreach (int hwnd in wins) { // do something }Pretty neat! Believe it or not, there's a method to all this madness. Everything makes sense once you get the hang of it. You can even use DllImport-style wrappers in managed C++. With .NET, you can move freely between managed and unmanaged worlds, as long as you do the proper translation. Most of the time you don't have to do anything; it just works. Occasionally, you need MarshalAs or a wrapper like GCHandle. For more on interop, read "Platform Invoke Tutorial" in the .NET documentation. ![]() ![]() Figure 4 ListWin Sample Q I'm writing an app that backs up files to a network drive. My program works unless a user has Microsoft Outlook® open; then my program can't copy the mailbox file (mailbox.pst) because Outlook opens it with exclusive access. I want to be able to kill Outlook if it's running. How can I kill a process cleanly? Jose Mercando A A program called tskill.exe (see Figure 5) comes with newer versions of Windows and it does what you want. ![]() Figure 5 tskill For example tskill outlookkills the running instance of Outlook. But if you want to write the code yourself, the safest way to kill a process is to post a WM_CLOSE message to its main window: HWND hwnd = // get main window PostMessage(hwnd, WM_CLOSE, 0, 0);Once the message is posted, you usually should wait for the process to actually terminate: HANDLE hp = OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE,FALSE,pid); WaitForSingleObject(hp, 5000); // wait up to 5 secondsWhen the process terminates, it flips to the signaled state and WaitForSingleObject returns WAIT_OBJECT_0. If it returns something else, the process is either hung or still doing something. In that case, the only way to kill the process is to pull out the big bazooka, TerminateProcess. if (WaitForSingleObject(hp, 5000) != WAIT_OBJECT_0) TerminateProcess(hp,0); // Hasta la vista, baby! ![]() DWORD bOKToKill = FALSE; SendMessageTimeout(hwnd, WM_QUERYENDSESSION, 0, 0, SMTO_ABORTIFHUNG|SMTO_NOTIMEOUTIFNOTHUNG, 100, &bOKToKill); if (bOKToKill) { // post WM_CLOSE and wait, etc. } ![]() ![]() CFindKillProcess fkp; DWORD pid = fkp.FindProcess("outlook.exe"); fkp.KillProcess(pid);FindProcess uses CProcessIterator and CProcessModuleIterator from last month's column to iterate the processes, looking for one whose first module (the EXE that started the process) matches the one you want to find: CProcessIterator itp; for (DWORD pid=itp.First(); pid; pid=itp.Next()) { CProcessModuleIterator itm(pid); HMODULE hModule = itm.First(); // .EXE if (/* module name = what I'm looking for */) { return pid; } } ![]() |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 창숨김
- 창숨김 다운
- 블로그 개설
- c#ㄱㄱ
- 블로그
- c#.net
- ubuntu
- C#책
- USB
- ROS2
- 창숨기기
- 블로그 이야기
- iptime 2000au
- C# 책
- 디스크 쓰기 금지되어 있습니다
- C 책
- c#
- ROS2 설치
- 블로그 공지
- Node
- readonly
- 창숨김 프로그램
- c#초보
- C# 속으루..
- ipTIME
- C#입문
- 책 소개
- 창숨기기 프로그램
- 2000au
- robot
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
글 보관함