2012-12-16

Ngày cuối năm ...


Ngày cuối năm,

Cũng những ngày này cách đây 4 năm mình đã xin nghỉ công ty mà mình đã làm việc 3 năm, vào đúng 1 ngày trước dịp xét tăng lương. Mọi người kêu sao không đợi qua Tết để nhận thưởng; mình cũng biết chứ, chỉ là cảm thấy rằng ko đợi được. 1 tháng lương đó + tiền tăng lương không đáng giá bằng việc hết phải chán nản từ nhà đi đi về về E-town suốt 2,3 tháng. Lúc đó mình đã từng viết rằng sẽ kiếm một cái gì đó vui vẻ để làm và cố gắng kiếm tiền còn những công việc buồn chán thì kệ nó.

Nói thì nói vậy nhưng hình như sau đó mình chỉ theo vế đầu. Không làm việc cho một công ty nào cả. Bạn bè kêu rằng mình  "lười" nhưng nghĩ lại mình đâu có lười, cũng làm từ trưa tới tối khuya, chỉ là lệch thời gian thôi. Thời gian rồi cũng trôi nhanh dù là làm "tạm bợ", hết tiền thì kiếm việc làm có tiền thì từ từ :D, rồi việc này việc khác, cũng vẫn đủ sống...

1 thời gian theo đuổi những không khí gọi là "mới so với trước đây" + thất bại trong việc mở quán game mất 1 số tiền cũng khá nhiều ... mình xác định mình sẽ lại đi làm thuê. Dù gì làm thuê vẫn còn có tiền đều đặn hàng tháng mà cũng nhẹ đầu hơn, đã vậy còn ra đường và ăn cơm trưa đều đặn hơn. Mình search trên 1 vòng, activate lại account trên mấy trang kiếm việc, rồi chuẩn bị CV dù chưa định gửi đi đâu. Quay qua quay lại cũng chỉ thấy E-Town, Quang Trung ... Lại lần nữa không cảm thấy thoải mái rồi từ chối các offer gọi đến.

Sau đó theo lời gọi của sếp và bạn bè cũ, mình về làm lại ở công ty trước đây, bên đó đang làm 1 số sp mới. Lúc đầu về thì cũng nhiều ý tưởng lắm, công ty cũng đã phát triển hơn. Nhưng rút cuộc 1 thời gian thì cũng chỉ làm 1 mình, phát triển mấy cái sản phẩm mà cuối cùng chẳng ai dùng. Việc thay đổi thói quen của người dùng không hề đơn giản. Năm đó đăng ký học chuyển đổi, rồi cưới vợ. Mọi việc cứ dần trôi qua ... và có lẽ đó là hai chuyện có thể coi là thành công trong năm. 1 năm sau công ty cũ "phá sản". Không phải phá sản theo nghĩa hết tiền, căn bản không phải do thiếu tiền mà thiếu hướng đi và thất bại trong định hướng. Những ngày cuối sếp có hỏi nên giữ lại nhiều developer không, mình nghĩ nên thông báo cho họ kiếm việc mới. Rồi sau đó công ty dọn VP sang Q.7, mình lại ở nhà làm, từ từ mất lửa và bỏ hẳn những việc làm dở version cho iOS, Android... Ý tưởng tốt thì sếp có nhưng có lẽ sếp nghĩ sai về IT. Nó không dễ đến nỗi ai cũng có thể làm và càng không phải là muốn làm gì thì làm. Quan trọng mình nghĩ chỉ đầu tư sơ sài trong khi mong muốn có 1 sản phẩm mang lại tiền là điều không thể.

Thời gian đó mình cũng về xem lại việc làm trong công ty đã cùng mở với thằng bạn. Dù gì công ty cũng có 1 chữ tên mình... 6 tháng sau tự nghĩ mình vẫn chưa thể giúp gì cho nó phát triển hơn mà công việc này giúp mình chán nản hơn và "ít tiền hơn" (tất nhiên)... mình lại tạm thời ra đi. Vẫn chưa thấy hướng đi và làm 1 công ty mà vẫn phải kiếm tiền chỗ khác để sống thì cũng ko khoái, cảm thấy không focus được. Lần thứ 2 lại chuẩn bị CV (update lại tình trạng hôn nhân), lại search và lại cảm thấy 1 chút gì đó lưỡng lự. Không phải sợ rằng mình không đáp ứng được công việc nhưng sợ rằng mình không phải là người thuộc 1 tổ chức nào đó. Đã 3 năm hầu không dậy sớm, công việc 1 mình, schedule chỉ có mỗi tên mình, không daily report... Công ty là một từ gì đó xa lạ và mình vẫn chần chờ...

Với tâm trạng đó xui rủi thế nào mình lại gặp 1 công ty làm 1 sp mới. Và bây giờ khoảng 1 năm sau ... lần thứ 3 nghĩ rằng mình sẽ chuẩn bị CV. Lần này là vì tiền, thời gian và quan trọng là cảm thấy không thoải mái. Đầu tiên, tiền là một phần quan trọng kéo nhiều thứ khác đi xuống, tất nhiên không phải là tất cả. Mình nghĩ mình không thể nào tiếp tục làm nếu quá tốn thời gian mà thu nhập lại không ổn định, không có đủ niềm tin. Mình có thể không nhận gì 1 thời gian nhất định. Nhưng cả team thì khác, tới tháng chưa nhận lương thì tinh thần xuống thảm, không thể làm gì trong cái không khí như vậy. Quan trọng hơn là cảm thấy thời gian trôi qua mà không làm được gì. Cảm thấy mất dần niềm tin từ gia đình người thân- những người đã tôn trọng lựa chọn của mình khi mình đã đánh đổi hướng đi này với những hướng đi khác.

Rút cuộc nhìn lại mình nhận thấy mình đang trốn chạy? Trốn chạy công việc đã từng làm là outsourcing, trốn chạy E-Town, Quang Trung, CNC... Nhìn lại mình thích làm cái gì đó khác, bị hấp dẫn bởi nhưng dự án làm sp mới. Mình cũng nhận ra những gì mình gặp thuộc 2 dạng có tiền thì lại không có những thứ khác (nhân sự cùi, kế hoạch, tiếp cận khách hàng) và ngược lại 1 số có thể làm được cái gì đó (chí ít cũng có người cho là vậy) thì lại không có tiền. Tất nhiên sống bằng niềm tin không xấu, chỉ có điều mình không nhận thấy những căn cứ mình có đủ để tin. Tóm lại là chưa đủ tầm và chưa đủ tài, mà ko dám đối mặt. Còn gia đình và nhiều thứ khác cần tiền... 3-4 năm trời không làm được gì, 4-5 tháng không có lương, mượn tiền vợ và bỏ ra cả vài trăm triệu :(. Không làm IT nữa thì bỏ việc và cũng không cần ở tp này nữa???

Cuối cùng năm 2012, nếu không tận thế thì mình vẫn sẽ theo những gì đã viết 4 năm trước nhưng có lẽ sẽ phải hành động khác.

2012-12-11

Windows Shell Extensions


Lâu lắm mới viết lại shell extension ...
Để viết Shell extensions thì có nhiều tutorials ko cần phải nói nữa, search 1 vòng trên CodeProject có thể list ra như series http://www.codeproject.com/Articles/441/The-Complete-Idiot-s-Guide-to-Writing-Shell-Extens hoặc các ví dụ trên CodePlex http://1code.codeplex.com/wikipage?title=WinShell

Hầu hết các ví dụ này dùng ATL, tất nhiên vậy cũng OK. Nhưng có 1 cách tiếp cận hay khá linh động viết COM bình thường không dùng ATL cần note lại là của TortoiseSVN. Đơn giản có thể tóm tắt như sau: ví dụ cần wrap 1 số shell extensions vào 1 chỉ DLL hoặc sử dụng trong trường hợp cần nhiều icon overlays mỗi cho 1 trạng thái của file như syncing, synced, do handler chỉ xử lý bằng IsMemberOf có nghĩa là chỉ xác định có/không hiển thị icon nên phải có 2 CLSID cho 2 trạng thái file.

Giả sử cần 2 icon overlay handlers cho syncing, synced và 1 context menu. Class xử lý icon overlay là IconOverlayExt và class xử lý context menu là ContextMenuExt. IconOverlayExt xử lý hiển thị hay không 2 trạng thái syncing và synced. Ta có các CLSID tương ứng CLSID_Synced, CLSID_Syncing, CLSID_ContextMenu.

Khai báo một enum xác định extension cần tạo:


enum ExtensionState
{
       Invalid,
       IconSynced,
       IconSyncing,
       ContextMenu
};


ShellExtClassFactory chịu trách nhiệm tại ext tùy theo loại ext cần tạo, nhận 1 ExtensionState là member


// This class factory object creates the main handlers -
// its constructor says which OLE class it has to make.
class ShellExtClassFactory : public IClassFactory
{
protected:
    ULONG _refCount;
    // Variable to contain class of object (i.e. syncing, synced, context menu)
    ExtensionState _stateToMake;
      
public:
    ShellExtClassFactory(ExtensionState stateToMake);
    virtual ~ShellExtClassFactory();
      
       // IUnknown members
    STDMETHODIMP         QueryInterface(REFIID, LPVOID FAR *);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
   
       // IClassFactory members
    STDMETHODIMP           CreateInstance(LPUNKNOWN, REFIID, LPVOID FAR *);
    STDMETHODIMP           LockServer(BOOL);
};


Như vậy code DllGetClassObject như sau:


STDAPI DllGetClassObject(const CLSID& rclsid, const IID& riid, void** ppv)
{
       if(ppv == 0)
       {
              return E_POINTER;
       }

       *ppv = NULL;

       // State to get factory
       ExtensionState stateToMake = ExtensionState::Invalid;
       if (IsEqualIID(rclsid, CLSID_Synced))
       {
              stateToMake = ExtensionState::IconSynced;
       }
       else if (IsEqualIID(rclsid, CLSID_Syncing))
       {
              stateToMake = ExtensionState::IconSyncing;
       }
       else if (IsEqualIID(rclsid, CLSID_ContextMenu))
       {
              stateToMake = ExtensionState::ContextMenu;
       }

       if (stateToMake != ExtensionState::Invalid)
       {
              ShellExtClassFactory *classFactory = new (std::nothrow)ShellExtClassFactory(stateToMake);
              if (classFactory == NULL)
              {
                     return E_OUTOFMEMORY;
              }

              // IMPORTANT NOTE: ref count set to 0 at this moment -> do not call Release
              // If your ClassFactory object's reference count initialize with 0, DO NOT call the release method,
              // it seems the COM library would call the release method automatically.
              // Otherwise, you'd get an access exception.
              //
              // If ClassFactory initialize with ref count = 1 -> must call Release
              // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms680760(v=vs.85).aspx
              const HRESULT hr = classFactory->QueryInterface(riid, ppv);
              if (FAILED(hr))
              {
                     delete classFactory;
              }            

              return hr;
       }

       return CLASS_E_CLASSNOTAVAILABLE;
}

NOTE: ở đây cũng lưu ý code COM trong TortoiseSVN khác với code mẫu ClassFactory hay COM hay thấy như trong MSDN chẳng hạn, sau khi QueryInterface sử dụng xong thường gọi Release tuy nhiên reference count TortoiseSVN init là zero trong khi ví dụ thông thường init là 1. Xem thêm http://msdn.microsoft.com/en-us/library/windows/desktop/ms680760(v=vs.85).aspx

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
    HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;

    if (IsEqualCLSID(CLSID_FileContextMenuExt, rclsid))
    {
        hr = E_OUTOFMEMORY;

        ClassFactory *pClassFactory = new ClassFactory();
        if (pClassFactory)
        {
            hr = pClassFactory->QueryInterface(riid, ppv);
            pClassFactory->Release();
        }
    }

    return hr;
}



Như vậy là OK đã có ClassFactory và ext cần tạo, trong CreateInstance


STDMETHODIMP ShellExtClassFactory::CreateInstance(LPUNKNOWN unkOuter,
       REFIID riid, LPVOID *out)
{
       if(out == 0)
       {
              return E_POINTER;
       }

       *out = NULL;

       // Shell extensions typically don't support aggregation (inheritance)     
       if (unkOuter)
       {
              return CLASS_E_NOAGGREGATION;
       }

       HRESULT hr;  
       if(_stateToMake == ExtensionState::ContextMenu)
       {
              // The shell will then call QueryInterface with IID_IShellExtInit--this
              // is how shell extensions are initialized.
              ContextMenuExt* contextMenu = new (std::nothrow)ContextMenuExt();  // create the ContextMenuExt object
              if (contextMenu == nullptr)
              {
                     return E_OUTOFMEMORY;
              }

              hr = contextMenu->QueryInterface(riid, out);
              if(FAILED(hr))
              {
                     delete contextMenu;
              }
       }
       else
       {
              // Create the main shell extension object.
              IconOverlayExt* iconOverlay = new (std::nothrow)IconOverlayExt(_stateToMake);  // create the IconOverlayExt object
              if (iconOverlay == nullptr)
              {
                     return E_OUTOFMEMORY;
              }

              hr = iconOverlay->QueryInterface(riid, out);
              if(FAILED(hr))
              {
                     delete iconOverlay;
              }
       }

       return hr;
}

Thêm std::nothrow để kiểm tra kết quả trả về thay vì nhận exception. Tại đây dựa vào ext cần tạo sẽ xử lý.

Còn 1 việc cần giải quyết là IPC giữa shell và app. Có thể dùng RPC, DLL shared segment cũng là 1 khả năng tuy nhiên phải cùng x86 hay 64-bit (app x86 chẳng hạn không thể load shell ext 64-bit), và named pipe có lẽ là hợp lý nhất.

Debug

Về debug chỉ có 1 lưu ý, nếu trong máy có install nhiều app có ext thì nên disable hay tạm thời uninstall. Giả sử máy bạn là developer hay cài TortoiseSVN thì tạm thời bỏ nó đi vì không thể nào debug được, lý do TortoiseSVN sẽ block ngay explorer khi reach break point đâu đó, khi đó chỉ có nước nhấn reset máy (cry).

Để tránh các app khác load ext: 1 số thằng rất hay load như Google Chrome, IDM và Skype trong DLL Attach code 1 đoạn như sau nếu có debugger hay là app test-shell của mình hoặc Explorer hay regsvr32 thì OK:

BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
{
#ifdef _DEBUG
       // If no debugger is present, then don't load the dll.
       // this prevents other apps from loading the dll and locking
       // it.

       bool isInShellTest = false;
       TCHAR fullPath[MAX_PATH + 1];       // MAX_PATH ok, the test really is for debugging anyway.
       DWORD len = GetModuleFileName(NULL, fullPath, MAX_PATH);
       TCHAR fileNameWithoutExt[MAX_PATH + 1];
       TCHAR ext[MAX_PATH + 1];
      
       _tsplitpath_s(fullPath, NULL, 0, NULL, 0, fileNameWithoutExt, MAX_PATH, ext, MAX_PATH);

       TCHAR fileName[MAX_PATH + 1];
       _tmakepath_s(fileName, MAX_PATH, NULL, NULL, fileNameWithoutExt, ext);

       if ((_tcsicmp(fileName, _T("test-shell.exe"))) == 0 || (_tcsicmp(fileName, _T("explorer.exe"))) == 0 ||
              (_tcsicmp(fileName, _T("verclsid.exe"))) == 0 || (_tcsicmp(fileName, _T("regsvr32.exe"))) == 0)
       {
              isInShellTest = true;
       }

       if (!::IsDebuggerPresent() && !isInShellTest)
       {
              return FALSE;
       }
#endif

       switch (reason)
       {
              ...
       }

       return TRUE;
}


Để có thể debug thực hiện:
Thêm Properties -> Build Events -> Post-Build Event:
Command Line: echo "Registering Icon Overlay Library" regsvr32 "$(TargetPath)"

Thêm Debugging
Command: explorer.exe

Để restart Explorer unload ext DLL không tắt bằng Taskbar Manager mà thực hiện
Ctrl + Shift + Right Click vào khoảng trống như hình bên dưới