Правильный способ передачи указателя на функцию P / Invoke - PullRequest
0 голосов
/ 18 июля 2010

Уважаемый опытный.Я разрабатываю сущность, которая позволяет пользователю асинхронно копировать несколько файлов с возможностью отмены (и также сообщать о прогрессе).Очевидно, что процесс копирования выполняется в другом потоке, отличном от потока, в котором был вызван CopyAsync.

Моя первая реализация использует FileStream.BeginRead / BeginWrite с буфером и сообщает о прогрессе в зависимости от количества использований этого буфера.

Позже, в образовательных целях, я пытался реализовать то же самое с помощью функции Win32 CopyFileEx.В конце концов я наткнулся на следующее: эта функция берет указатель на значение bool, которое рассматривается как индикатор отмены.Согласно MSDN, это значение должно быть проверено несколько раз Win32 во время операции копирования.Когда пользователь устанавливает это значение в «ложь», операция копирования отменяется.

Реальная проблема для меня состоит в том, как создать логическое значение, передать его Win32 и сделать это значение доступным для внешнего пользователя, чтобы дать емувозможность отменить операцию копирования.Очевидно, что пользователь вызовет CancelAsync (object taskId), поэтому мой вопрос о том, как получить доступ к этому логическому значению в другом потоке для моей реализации CancelAsync.

Моя первая попытка состояла в том, чтобы использовать Dictionary, где key является идентификаторомасинхронной операции и значения указывает на выделенный для слота памяти логическое значение.Когда пользователь вызывает метод «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 и всех небезопасных вещах, поэтому колеблюсьо последнем подходе для удержания ссылки на локальное значение в словаре и предоставления этого значения другому потоку.

Любые другие мысли о том, как выставить этот указатель на логическое значение для поддержки отмены операции копирования?

Ответы [ 3 ]

1 голос
/ 18 июля 2010

Ах, так вот о чем эта другая тема. Есть гораздо лучший способ сделать это, CopyFileEx () также поддерживает обратный вызов прогресса. Этот обратный вызов позволяет обновить пользовательский интерфейс, чтобы показать прогресс. И это позволяет вам отменить копию, просто верните PROGRESS_CANCEL из обратного вызова.

Посетите pinvoke.net для получения декларации делегата обратного вызова, которая вам понадобится.

0 голосов
/ 19 июля 2010

Возможно, я что-то упускаю, но почему вы не можете просто использовать defn уже @ http://pinvoke.net/default.aspx/kernel32/CopyFileEx.html, а затем установить ref int (pbCancel) в 1 во время отмены?

0 голосов
/ 18 июля 2010

Если ваша цель заключается в поддержке возможности отменить текущую операцию копирования файла, я рекомендую использовать CopyProgressRoutine .Это регулярно вызывается во время копирования и позволяет отменить операцию с кодом возврата.Это позволит вам отменить операцию асинхронно, не имея дело с указателями напрямую.

private class FileCopy
{
    private bool cancel = false;

    public void Copy(string existingFile, string newFile)
    {
        if (!CopyFileEx(existingFile, newFile, 
            CancelableCopyProgressRoutine, IntPtr.Zero, IntPtr.Zero, 0))
        {
            throw new Win32Exception();
        }
    }

    public void Abort()
    {
        cancel = true;
    }

    private CopyProgressResult CancelableCopyProgressRoutine(
        long TotalFileSize,
        long TotalBytesTransferred,
        long StreamSize,
        long StreamBytesTransferred,
        uint dwStreamNumber,
        CopyProgressCallbackReason dwCallbackReason,
        IntPtr hSourceFile,
        IntPtr hDestinationFile,
        IntPtr lpData)
    {
        return cancel ? CopyProgressResult.PROGRESS_CANCEL : 
            CopyProgressResult.PROGRESS_CONTINUE;
    }

    // Include p/inovke definitions from 
    // http://www.pinvoke.net/default.aspx/kernel32.copyfileex here
}

Если вы хотите использовать аргумент pbCancel, то ручное выделение неуправляемой памяти, как вы уже делаете, вероятно, самый безопасный способ сделать это.Получение адреса локальной переменной немного опасно, потому что указатель больше не будет действительным, когда переменная выходит из области видимости.

Вы также можете использовать логическое поле в объекте, а не логическую локальную переменную, но вам нужно будет закрепить его в памяти, чтобы предотвратить его перемещение сборщиком мусора.Вы можете сделать это либо с помощью оператора fixed , либо с помощью GCHandle.Alloc .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...