首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

持久化 Open 對話框視圖狀態與混合模式 DLL 托管程式碼

     編輯:關於VC++

第一部分:修正 CPersistOpenDlg 視圖狀態儲存 Bug

問題現象

自訂 Open 對話框 (`CPersistOpenDlg`) 子類化 `SHELLDLL_DefView` 以記住視圖模式 (縮略圖/清單/詳細資料)。但發現:切換資料夾時會銷毀重建 View,導致 `OnDestroy` 儲存的是舊資料夾的視圖模式,而非關閉對話框時的模式。

根因分析

  • `CListViewShellWnd::OnDestroy` 在 View 銷毀時儲存模式
  • 對話框關閉時並未觸發 View 的 `OnDestroy` (已在切換資料夾時觸發)
  • 對話框物件銷毀時寫入的是過期的 `m_lastViewMode`

解決方案:重新子類化機制


class CListViewShellWnd : public CWnd {
    CPersistOpenDlg* m_pDialog; // 回指父對話框
    UINT m_lastViewMode;
    
    void OnDestroy() {
        m_lastViewMode = GetViewMode(); // 記住當前模式
        // 關鍵:發送自訂訊息要求重新子類化新 View
        m_pDialog->PostMessage(MYWM_POSTINIT); 
    }
};

class CPersistOpenDlg : public CFileDialog {
    CListViewShellWnd m_wndListViewShell;
    bool m_bFirstInit = true;
    
    LRESULT OnPostInit(WPARAM wParam, LPARAM) {
        if (wParam) { // 首次初始化
            SubclassShellView();
            RestoreViewMode(); // 從登錄還原
        } else { // View 重建後
            SubclassShellView(); // 僅重新子類化,不還原模式
        }
        return 0;
    }
    
    void SubclassShellView() {
        HWND hView = FindShellView();
        if (hView && m_wndListViewShell.m_hWnd != hView) {
            m_wndListViewShell.UnsubclassWindow();
            m_wndListViewShell.SubclassWindow(hView);
        }
    }
};

關鍵點

  • 使用 `PostMessage` 而非 `SendMessage`:確保對話框先建立新 View,再進行子類化
  • 區分首次/後續初始化:`WPARAM` 傳遞 `TRUE`/`FALSE`
  • 對話框銷毀時郵件佇列隨之消失,多餘的 `PostMessage` 無害

第二部分:MFC DLL 中使用 Managed Extensions (C++/CLI)

連結器警告 LNK4243

warning LNK4243: DLL 包含 /clr 編譯物件,無法搭配 /NOENTRY 連結;映像可能無法正常執行

根本原因

  • 純 Managed DLL 預設 `/NOENTRY` (無 `DllMain`),避免在載入鎖定期間執行 Managed 程式碼
  • MFC DLL 需要 `DllMain` (透過 `_DllMainCRTStartup`) 初始化靜態物件、`CWinApp` 等
  • 兩者衝突:`/clr` 強制 `/NOENTRY`,但 MFC 需要 Entry Point

解決方案:手動初始化/終止函式

步驟 1:專案設定

  • 連結器 → 輸入 → 強制符號引用:移除 `__DllMainCRTStartup@12` (若有)
  • 連結器 → 命令列 → 附加選項:加入 `/NOENTRY`
  • C/C++ → 一般 → Common Language Runtime 支援:`/clr`

步驟 2:匯出初始化函式


// MixedDLL.h
#ifdef MIXEDDLL_EXPORTS
#define MIXED_API __declspec(dllexport)
#else
#define MIXED_API __declspec(dllimport)
#endif

MIXED_API BOOL InitializeMixedDLL();
MIXED_API BOOL TerminateMixedDLL();

// MixedDLL.cpp
#include <_vcclrit.h> // CRT 內部標頭

BOOL InitializeMixedDLL() {
    return __crt_dll_initialize(); // 相當於 _DllMainCRTStartup(DLL_PROCESS_ATTACH)
}

BOOL TerminateMixedDLL() {
    return __crt_dll_terminate();  // 相當於 _DllMainCRTStartup(DLL_PROCESS_DETACH)
}

步驟 3:應用程式端呼叫


// 應用程式主視窗或 CWinApp::InitInstance
class CMixedDLLInitializer {
public:
    CMixedDLLInitializer() { InitializeMixedDLL(); }
    ~CMixedDLLInitializer() { TerminateMixedDLL(); }
};

// 全域實例,確保在 main/WinMain 前初始化,結束後終止
CMixedDLLInitializer g_mixedDLLInit;

進階情境:DLL 被另一個 Native DLL 載入

若 Mixed DLL 由另一個 Native DLL 隱性連結載入,不能在全域建構子中呼叫 `__crt_dll_initialize` (會造成載入器死結)。解決方式:

  • Native DLL 的 `DllMain` 中 `DLL_PROCESS_ATTACH` 時顯式呼叫 `InitializeMixedDLL()`
  • 或 Native DLL 匯出自己的 `InitMixedDLL()`/`TermMixedDLL()`,由 EXE 呼叫

驗證追蹤輸出


// MixedDLL.cpp
#define TRACE_MIXED(msg) TRACE(_T("MixedDLL: %s\n"), _T(msg))

CMixedDLLInitializer::CMixedDLLInitializer() {
    // 錯誤示範:TRACE 在 CRT 初始化前呼叫會 Crash
    // TRACE_MIXED("Before init"); 
    InitializeMixedDLL();
    TRACE_MIXED("After init"); // 正確:CRT 已初始化
}

參考資源

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved