为 P/Invoke 函数传递指针的正确方法
亲爱的高手。我正在开发一个实体,它允许用户以异步方式复制多个文件,并具有取消功能(并报告进度)。显然,复制过程在另一个线程中运行,与调用 CopyAsync 的线程不同。
我的第一个实现使用带有缓冲区的 FileStream.BeginRead/BeginWrite 并根据该缓冲区的使用次数报告进度。
后来,出于教育目的,我尝试通过 Win32 CopyFileEx 函数实现相同的内容。最终,我偶然发现了以下事情:该函数采用一个指向 bool 值的指针,该值被视为取消指示器。根据 MSDN,Win32 在复制操作期间将多次检查该值。当用户将此值设置为“假”时,复制操作将被取消。
对我来说真正的问题是如何创建一个布尔值,将其传递给 Win32 并使外部用户可以访问该值,从而使他能够取消复制操作。显然,用户将调用 CancelAsync(object taskId),所以我的问题是如何从我的 CancelAsync 实现中访问另一个线程中的布尔值。
我的第一次尝试是使用字典,其中键是异步操作的标识符,值指向为布尔值内存槽分配的值。当用户调用“CancelAsync(object taskId)”方法时,我的类从字典中检索指向已分配内存的指针,并在那里写入“1”。
昨天,我开发了另一个解决方案,该解决方案基于在我的复制方法中创建一个 bool 局部变量并将该值的地址保存在字典中,直到复制操作完成。这种方法可以用下面的代码行来描述(非常简单和粗略,只是为了说明一个想法):
class Program
{
// dictionary for storing operaitons identifiers
public Dictionary<string, IntPtr> dict = new Dictionary<string,IntPtr>();
static void Main(string[] args)
{
Program p = new Program();
p.StartTheThread(); // start the copying operation, in my
// implementation it will be a thread pool thread
}
ManualResetEvent mre;
public void StartTheThread()
{
Thread t = new Thread(ThreadTask);
mre = new ManualResetEvent(false);
t.Start(null);
GC.Collect(); // just to ensure that such solution works :)
GC.Collect();
mre.WaitOne();
unsafe // cancel the copying operation
{
IntPtr ptr = dict["one"];
bool* boolPtr = (bool*)ptr; // obtaining a reference
// to local variable in another thread
(*boolPtr) = false;
}
}
public void ThreadTask(object state)
{
// In this thread Win32 call to CopyFileEx will be
bool var = true;
unsafe
{
dict["one"] = (IntPtr)(&var); // fill a dictionary
// with cancellation identifier
}
mre.Set();
// Actually Win32 CopyFileEx call will be here
while(true)
{
Console.WriteLine("Dict:{0}", dict["one"]);
Console.WriteLine("Var:{0}", var);
Console.WriteLine("============");
Thread.Sleep(1000);
}
}
}
实际上,我对 P/Invoke 和所有不安全的东西有点陌生,所以对后一种保存对本地引用的方法犹豫不决字典中的值并将该值公开给另一个线程。
关于如何将该指针公开为布尔值以支持取消复制操作还有其他想法吗?
Dear skilled. I’m developing an entity which allows user to copy multiple files in async manner with cancellation ability (and reporting progress as well). Obviously the process of copying runs in another thread, different from thread where CopyAsync was called.
My first implementation uses FileStream.BeginRead/BeginWrite with a buffer and reporting progress against number of usages of that buffer.
Later, for education purposes, I was trying to implement the same stuff thru Win32 CopyFileEx function. Eventually, I’ve stumbled upon the following thing: this function takes a pointer to bool value which is treated as cancellation indicator. According to MSDN this value is to be examined multiple times by Win32 during copying operation. When user sets this value to “false” the copying operation is cancelled.
The real problem for me is how to create a boolean value, pass it to Win32 and to make this value accessible for external user to give him an ability to cancel the copying operation. Obviously the user will call CancelAsync(object taskId), so my question is about how to get access to that boolean value in another thread fro my CancelAsync implementation.
My first attempt was to use Dictionary where key is an identifier of async operation and value points to allocated for boolean value memory slot. When user calls “CancelAsync(object taskId)” method, my class retrieves a pointer to that allocated memory from dictionary and writes “1” there.
Yesterday I’ve developed another solution which is based on creating a bool local variable in my method of copying and holding the address of that value in dictionary until copying operation completes. This approach could be described in the following lines of code (very simple and rough, just to illustrate an idea):
class Program
{
// dictionary for storing operaitons identifiers
public Dictionary<string, IntPtr> dict = new Dictionary<string,IntPtr>();
static void Main(string[] args)
{
Program p = new Program();
p.StartTheThread(); // start the copying operation, in my
// implementation it will be a thread pool thread
}
ManualResetEvent mre;
public void StartTheThread()
{
Thread t = new Thread(ThreadTask);
mre = new ManualResetEvent(false);
t.Start(null);
GC.Collect(); // just to ensure that such solution works :)
GC.Collect();
mre.WaitOne();
unsafe // cancel the copying operation
{
IntPtr ptr = dict["one"];
bool* boolPtr = (bool*)ptr; // obtaining a reference
// to local variable in another thread
(*boolPtr) = false;
}
}
public void ThreadTask(object state)
{
// In this thread Win32 call to CopyFileEx will be
bool var = true;
unsafe
{
dict["one"] = (IntPtr)(&var); // fill a dictionary
// with cancellation identifier
}
mre.Set();
// Actually Win32 CopyFileEx call will be here
while(true)
{
Console.WriteLine("Dict:{0}", dict["one"]);
Console.WriteLine("Var:{0}", var);
Console.WriteLine("============");
Thread.Sleep(1000);
}
}
}
Actually I’m a bit new to P/Invoke and all unsafe stuff so hesitating about latter approach for holding a reference to local value in dictionary and exposing this value to another thread.
Any other thoughts on how to expose that pointer to boolean in order to support cancellation of copying operation?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
啊,这就是另一个线程的内容。有一种更好的方法可以实现此目的,CopyFileEx() 还支持进度回调。该回调允许您更新 UI 以显示进度。它允许您取消复制,只需从回调中返回 PROGRESS_CANCEL 即可。
访问 pinvoke.net 获取您需要的回调委托声明。
Ah, so that's what that other thread was about. There's a much better way to accomplish this, CopyFileEx() also supports a progress callback. That callback allows you to update the UI to show progress. And it allows you to cancel the copy, just return PROGRESS_CANCEL from the callback.
Visit pinvoke.net for the callback delegate declaration you'll need.
如果您的目标是支持取消正在进行的文件复制操作,我建议使用 CopyProgressRoutine。在复制过程中会定期调用此函数,并允许您使用返回代码取消操作。它可以让您异步取消操作,而不必直接处理指针。
如果您确实想使用 pbCancel 参数,那么像您已经在做的那样手动分配非托管内存可能是最安全的方法。获取局部变量的地址有点危险,因为一旦变量超出范围,指针将不再有效。
您还可以在对象中使用布尔字段而不是布尔局部变量,但您需要将其固定在内存中以防止垃圾收集器移动它。您可以使用 fixed 语句来执行此操作或使用GCHandle.Alloc。
If your goal is to support being able to cancel a file copy operation in progress, I recommend using a CopyProgressRoutine. This gets called regularly during the copy, and allows you to cancel the operation with a return code. It will let you cancel the operation asynchronously without having to deal with pointers directly.
If you do want to use the pbCancel argument, then manually allocating unmanaged memory as you are already doing is probably the safest way to do it. Taking the address of a local variable is a little dangerous because the pointer will no longer be valid once the variable goes out of scope.
You could also use a boolean field in an object rather than a boolean local variable, but you will need to pin it in memory to prevent the garbage collector from moving it. You can do this either using the fixed statement or using GCHandle.Alloc.
也许我遗漏了一些东西,但为什么你不能直接使用 defn @ http ://pinvoke.net/default.aspx/kernel32/CopyFileEx.html 然后在取消时将 ref int (pbCancel) 设置为 1?
Perhaps I'm missing something, but why couldn't you just use the defn already @ http://pinvoke.net/default.aspx/kernel32/CopyFileEx.html and then set the ref int (pbCancel) to 1 at cancel time?