为什么我的CreateThread输入参数被破坏了?

时间:2016-11-30 00:20:10

标签: multithreading winapi windows-shell

在命名空间扩展中,我正在创建一个线程并将文件路径传递给线程函数。 我看到的问题是文件路径的第一个字符被破坏。 D:\ temp0.csv传入并在线程函数中变为Y:\ temp0.csv或其他一些随机损坏的第一个wchar。在Win2k8R2和Win10中它运行良好,但有时它会以同样的方式失败。我试过禁用优化无济于事。 fileName变量从来自IFileOpenDialog的IShellItem填充。 我需要做些什么来解决这个问题?

线程函数调用者:

LPWSTR filePath       = NULL;
IFileOpenDialog *ofd  = NULL;
IShellItem *file      = NULL;
hrPath                = file->GetDisplayName(SIGDN_FILESYSPATH, &filePath);
CreateThread(NULL, 0, CCsv::BuildTree, static_cast<LPVOID>(filePath), 0, NULL);

静态类线程函数

DWORD WINAPI CCsv::BuildTree(LPVOID lpParam) {
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    LPWSTR filePath = static_cast<LPWSTR>(lpParam);
}

这是一个最小程序,但它不会重复使用此代码。一个区别是我添加了等待线程功能。在名称空间扩展名中不存在。

的main.cpp

// buf.cpp : Defines the entry point for the console application.
//

#pragma once
#include "stdafx.h"


using std::wstring;
static CRITICAL_SECTION g_TreeLock;
static CRITICAL_SECTION g_MountQueueLock;


class CCsv 
{
public:
    CCsv();
    ~CCsv();
    static DWORD WINAPI BuildTree(LPVOID lpParam);
};
class CMountPath {
public:
    CMountPath();
    ~CMountPath();
    BOOL Mount();
    BOOL PathExists(LPWSTR path);
private:
    CSimpleArray<wstring>   m_MountQueue;
};
extern CCsv g_Csv;

CCsv::CCsv() {
    InitializeCriticalSection(&g_TreeLock);
}

CCsv::~CCsv() {
    DeleteCriticalSection(&g_TreeLock);
}
DWORD WINAPI CCsv::BuildTree(LPVOID lpParam) {
    LPWSTR name = static_cast<LPWSTR>(lpParam);
    MessageBox(NULL, name, L"", MB_OK);
    CoTaskMemFree(name);
    return 0;
}

CMountPath::CMountPath() {
    InitializeCriticalSection(&g_MountQueueLock);
}

CMountPath::~CMountPath() {
 DeleteCriticalSection(&g_MountQueueLock);
}
BOOL CMountPath::PathExists(LPWSTR path) {
    return FALSE;
}
BOOL CMountPath::Mount() {
    IEnumIDList *idl                    = NULL;
    LPITEMIDLIST pidl                   = NULL;
    LPITEMIDLIST desktopPidl            = NULL;
    LPCITEMIDLIST pidlRelative          = NULL;
    BOOL success                        = FALSE;
    HRESULT hr, hrPath                  = S_FALSE;
    LPWSTR filePath                     = NULL;
    PWSTR filePathHeap                  = NULL;
    WCHAR msg[MAXPATH+MAXMSG]           = {0};
    IFileOpenDialog *ofd                = NULL;
    IShellItem *file                    = NULL;
    DWORD idx                           = 0;
    BOOL isQueued                       = FALSE;

    const COMDLG_FILTERSPEC fileSpec[]  = { 
            { L"CSV Text Files", L"*.csv" },
            { L"All Files", L"*.*" },
    };
    hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ofd));
    if (SUCCEEDED(hr)) {
        if (SUCCEEDED(hr)){
            ofd->SetTitle(L"Choose file");
            ofd->SetFileTypes(ARRAYSIZE(fileSpec), fileSpec);
            hr = ofd->Show(NULL);
            if(SUCCEEDED(hr)) 
                hr = ofd->GetResult(&file);
            if(SUCCEEDED(hr)) 
                hrPath = file->GetDisplayName(SIGDN_FILESYSPATH, &filePath);
            if(SUCCEEDED(hrPath)){
                LPWSTR filePathHeap = (LPWSTR)CoTaskMemAlloc(MAXPATH * sizeof(WCHAR));
                if(filePathHeap) {
                    StringCchCopy(filePathHeap, MAXPATH, filePath);
                    if(PathExists(filePathHeap)) {
                        StringCchPrintf(msg, MAXPATH+MAXMSG, L"The file %s is already loaded.", filePathHeap);
                        MessageBox(NULL, msg, L"appname", MB_OK);
                    }
                    else {
                        EnterCriticalSection(&g_MountQueueLock);
                        isQueued = !m_MountQueue.Find(wstring(filePathHeap)) ? TRUE : FALSE;
                        if(!isQueued)
                            m_MountQueue.Add(wstring(filePathHeap));
                        LeaveCriticalSection(&g_MountQueueLock);
                        if(!isQueued) {
                            HANDLE hThread = CreateThread(NULL, 0, CCsv::BuildTree, static_cast<LPVOID>(filePathHeap), 0, NULL);
                            // there is no wait in the namespace extension. the wait is just to keep the console app main thread running.
                            if(INVALID_HANDLE_VALUE != hThread)
                                WaitForSingleObject(hThread, INFINITE);
                        }
                        else {
                            StringCchPrintf(msg, MAXPATH+MAXMSG, L"The file %s is already being loaded.", filePathHeap);
                            MessageBox(NULL, msg, L"appname", MB_OK);
                        }
                    }
                }
                CoTaskMemFree(filePath);
                file->Release();
            }
        }
        ofd->Release();
    }

    return success;
}

int main() {
    CoInitialize(NULL);
    CMountPath m;
    m.Mount();
    CoUninitialize();

    return 0;
}

stdafx.h中

#pragma once
#define MAXPATH 32767 
#define MAXMSG 128
#define WIN32_LEAN_AND_MEAN
#define WINVER 0x0600 
#define _WIN32_WINNT 0x0600

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

#include <windows.h>
#include <atlbase.h>
#include <atlstr.h>
#include <atlcoll.h>
#include <shlobj.h>
#include <Shobjidl.h>
#include <ShlGuid.h>
#include <shellapi.h>
#include <OleAuto.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <string>

2 个答案:

答案 0 :(得分:2)

你为什么要使用线程?当您生成一个新线程时,您将阻止代码等待线程终止,然后继续,因此您将序列化所有代码。你甚至可能根本不使用线程。

此外,您的代码中存在内存泄漏和各种逻辑错误。

请改为尝试:

// buf.cpp : Defines the entry point for the console application.
//

#pragma once
#include "stdafx.h"

using std::wstring;

class CCsv 
{
public:
    CCsv();
    ~CCsv();

    void BuildTree(LPCWSTR name);

private:
    CRITICAL_SECTION m_TreeLock;
};

class CMountPath {
public:
    CMountPath();
    ~CMountPath();

    BOOL Mount();
    BOOL PathExists(LPCWSTR path);

private:
    CSimpleArray<wstring> m_MountQueue;
    CRITICAL_SECTION m_MountQueueLock;
};

CCsv g_Csv;

CCsv::CCsv() {
    InitializeCriticalSection(&m_TreeLock);
}

CCsv::~CCsv() {
    DeleteCriticalSection(&m_TreeLock);
}

void CCsv::BuildTree(LPCWSTR name) {
    MessageBoxW(NULL, name, L"", MB_OK);
}

CMountPath::CMountPath() {
    InitializeCriticalSection(&m_MountQueueLock);
}

CMountPath::~CMountPath() {
    DeleteCriticalSection(&m_MountQueueLock);
}

BOOL CMountPath::PathExists(LPCWSTR path) {
    return FALSE;
}

BOOL CMountPath::Mount() {
    BOOL success                        = FALSE;
    HRESULT hr                          = S_FALSE;
    LPWSTR filePath                     = NULL;
    WCHAR msg[MAXPATH+MAXMSG]           = {0};
    IFileOpenDialog *ofd                = NULL;
    IShellItem *file                    = NULL;
    BOOL isQueued                       = FALSE;

    const COMDLG_FILTERSPEC fileSpec[]  = { 
            { L"CSV Text Files", L"*.csv" },
            { L"All Files", L"*.*" },
    };

    hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ofd));
    if (SUCCEEDED(hr)) {
        ofd->SetTitle(L"Choose file");
        ofd->SetFileTypes(ARRAYSIZE(fileSpec), fileSpec);
        hr = ofd->Show(NULL);
        if(SUCCEEDED(hr)) 
            hr = ofd->GetResult(&file);
        if(SUCCEEDED(hr)) {
            hr = file->GetDisplayName(SIGDN_FILESYSPATH, &filePath);
            if(SUCCEEDED(hr)){
                if(PathExists(filePath)) {
                    StringCchPrintf(msg, ARRAYSIZE(msg), L"The file %s is already loaded.", filePath);
                    MessageBox(NULL, msg, L"appname", MB_OK);
                }
                else {
                    EnterCriticalSection(&m_MountQueueLock);
                    isQueued = !m_MountQueue.Find(filePath) ? TRUE : FALSE;
                    if(!isQueued)
                        m_MountQueue.Add(filePath);
                    LeaveCriticalSection(&m_MountQueueLock);
                    if(!isQueued) {
                        CCsv::BuildTree(filePath);
                    }
                    else {
                        StringCchPrintf(msg, ARRAYSIZE(msg), L"The file %s is already being loaded.", filePath);
                        MessageBox(NULL, msg, L"appname", MB_OK);
                    }
                }
                CoTaskMemFree(filePath);
            }
            file->Release();
        }
        ofd->Release();
    }

    return success;
}

int main() {
    CoInitialize(NULL);
    CMountPath m;
    m.Mount();
    CoUninitialize();

    return 0;
}

答案 1 :(得分:0)

你的概念错误:

if(!isQueued) 
{
  m_MountQueue.Add(wstring(filePathHeap));
  CreateThread(NULL, 0, CCsv::BuildTree, static_cast<LPVOID>(filePathHeap), 0, NULL);
}

所以你在某个数据库中插入filePathHeap并同时将此filePathHeap传递给某个线程。问题 - 谁是filePathHeap的所有者?谁必须释放它?如果你从BuildTree释放它 - 在m_MountQueue中将是无效指针?您没有显示代码以及处理m_MountQueue的代码 - 在filePathHeap中使用之前,此代码可能会弹出并免费BuildTree

一般情况下 - 如果你将filePathHeap推送到m_MountQueue - 在此之后你不能直接使用它指针,而是有工作线程( pool )来自{ {1}},处理并释放它。或者如果您直接使用m_MountQueue,则不得将其插入filePathHeap

如果您需要同时使用多个线程中的m_MountQueue - 您需要重新计数。有人这样:

filePathHeap

其次 - 这段代码

class CFilePath 
{
    PWSTR _filePath;
    LONG _dwRef;

    ~CFilePath()
    {
        CoTaskMemFree(_filePath);
    }

public:

    PCWSTR getPath() { return _filePath; }

    CFilePath(PWSTR filePath)
    {
        _filePath = filePath;
        _dwRef = 1;
    }

    void AddRef()
    {
        InterlockedIncrement(&_dwRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRef)) delete this;
    }
};

ULONG CALLBACK CCsv::BuildTree(CFilePath* p)
{
    MessageBoxW(0, p->getPath(), L"use path in thread 2", MB_OK);
    p->Release();
    return 0;
}

BOOL CMountPath::Mount() {

...
    if (CFilePath* p = new CFilePath(filePath))
    {
        p->AddRef();

        if (HANDLE hThread = CreateThread(0, 0, reinterpret_cast<PTHREAD_START_ROUTINE>(CCsv::BuildTree), p, 0, 0))
        {
            CloseHandle(hThread);
        }
        else
        {
            p->Release();
        }

        MessageBoxW(0, p->getPath(), L"use path in thread 1", MB_OK);
        p->Release();
    }
    else
    {
        CoTaskMemFree(filePath);
    }
...
}
绝对没必要。您可以直接使用LPWSTR filePathHeap = (LPWSTR)CoTaskMemAlloc(MAXPATH * sizeof(WCHAR)); if(filePathHeap) StringCchCopy(filePathHeap, MAXPATH, filePath); ,但不能重新分配它。为了什么?!

第三 - 正如之前的回答 - 几乎没有意义创建线程,然后等待它退出。

filePath

为什么在这种情况下不能直接调用 HANDLE hThread = CreateThread(NULL, 0, CCsv::BuildTree, static_cast<LPVOID>(filePathHeap), 0, NULL); // there is no wait in the namespace extension. the wait is just to keep the console app main thread running. if(INVALID_HANDLE_VALUE != hThread) WaitForSingleObject(hThread, INFINITE); 同样的效果?

还可以注意到CCsv::BuildTree(filePathHeap);仅针对INVALID_HANDLE_VALUECreateFile返回 - 因此仅针对文件句柄。创建api的所有另一个对象(包括Socket在出错时返回0,但不是CreateThread( - 1)) - 所以你错误地检查错误条件。 最后谁会打电话给INVALID_HANDLE_VALUE?线程退出时句柄不自动关闭