由ActiveX DLL中的全局變量想到的...


    在使用VB6編寫類或控件時,有一個不如意的地方,那就是無法在屬性或函數中返回結構體。一般情況下,我們可以使用一個新的類來模擬結構體,比如有一個表格類,類名爲clsGrid,它包含一個行屬性Rows,而Rows又包含Height等成員屬性,此時,我們需要定義一個類clsRows,代碼大致如下:
    類clsGrid:
    Dim m_objRows As clsRows

    Private Sub Class_Initialize()
        Set m_objRows = New clsRows
    End Sub
    Private Sub Class_Terminate()
        Set m_objRows = Nothing
    End Sub
    Public Property Get Rows(Optional ByVal Index as Long) As clsRows
        Set Rows = m_objRows
    End Property

    類clsRows:
    Dim m_lngHeight As Long

    Public Property Get Height() As Long
        Height = m_lngHeight
    End Property
    Public Property Let Height(ByVal New_Value As Long)
        m_lngHeight = New_Value
    End Property

    OK,一切看上去很不錯,但問題來了,當類clsRow的成員屬性Height改變時,需要通知類clsGrid,以便類clsGrid在Class_Paint事件中按新的Height屬性繪製,怎麼辦?
    相信大家已經想到了答案,那就是讓clsRows拋出事件,或者定義一個全局變量,讓clsGrid和clsRows都可以讀寫。
    如果只是一個clsRows實例,拋出事件也是一個不錯的選擇,然而,這是一個表格類,行對象(即clsRows可能會超過10萬個),很明顯,拋出事件是一種低效的選擇,而且更重要的是,由於事件無法使用Friend而進行內部封裝,會對應用程序員編寫應用代碼時造成不必要的誤解。
    那麼,就用全局變量吧。
    我們先增加一個標準模塊,然後定義一個全局變量。

    標準模塊modDeclare:
    Global g_lngHeight() As Long

    類clsGrid:
    Private Sub Class_Initialize()
        Set m_objRows = New clsRows
        Redim g_lngHeight(1000)
    End Sub
    Private Sub Class_Terminate()
        Set m_objRows = Nothing
    End Sub
    Public Property Get Rows(Optional ByVal Index as Long) As clsRows
        m_objRows.RowIndex = Index
        Set Rows = m_objRows
    End Property
    Public Sub Class_Paint()
        Dim i as Long
        For i=0 To UBound(g_lngHeight)
            '繪圖代碼略
        Next
    End Sub

    類clsRows:
    Public RowIndex As Long

    Public Property Get Height() As Long
        Height = g_lngHeight(RowIndex)
    End Property
    Public Property Let Height(ByVal New_Value As Long)
        g_lngHeight(RowIndex) = New_Value
    End Property

    到這裏,代碼基本實現了設計任務,看來可以編譯打包後交差了,打包工程暫命名爲MYGrid。

    接下來,我們將使用MYGrid.Grid編寫一個應用,先建一個測試工程,工程裏只包含一個標準模塊,測試代碼如下:
    Sub Main()
        Dim o As MYGrid.Grid
        Set o = New MYGrid.Grid
        o.Rows(1).Height = 100
        Debug.Print o.Rows(1).Height
        Set o = Nothing
    End Sub
    運行後,立即窗口如願輸出了100,結果正確。
    我們再將代碼修改一下:
    Sub Main()
        Dim o1 As MYGrid.Grid
        Dim o2 As MYGrid.Grid
        Set o1 = New MYGrid.Grid
        Set o2 = New MYGrid.Grid
        o1.Rows(1).Height = 100
        o2.Rows(1).Height = 200
        Debug.Print o1.Rows(1).Height,o2.Rows(1).Height
        Set o1 = Nothing
        Set o2 = Nothing
    End Sub
    立即窗口輸出200,200,這個結果明顯是錯的,爲什麼會這樣呢?
    原來問題出在全局變量裏,在ActiveX DLL工程裏,不管應用程序創建了多個接口實例,而工程實例只有一個,也就是說,全局變量g_lngRows將被實例o1和o2共同使用,這樣一來,o1和o2的屬性值就成了一個了。
    如何解決這個問題呢?
    答案當然是避免在ActiveX DLL 工程中使用全局變量,而是在類clsGrid中聲明數組lngHeight(),然而這樣一來,類clsRows卻無法訪問lngHeight()了,怎麼辦?
    我們也許會想到在clsGrid中傳遞一個引用給clsRows,比如:
    類clsGrid:
    Dim m_lngHeight() As Long
    Private Sub Class_Initialize()
        Set m_objrows = New clsRows
        ReDim m_lngHeight(1000)
        m_objrows.BindArray m_lngHeight
    End Sub
    Private Sub Class_Terminate()
        Set m_objrows = Nothing
    End Sub
    Public Property Get Rows(Optional ByVal Index As Long) As clsRows
        m_objrows.RowIndex = Index
        Set Rows = m_objrows
    End Property
    Public Sub Class_Paint()
        Dim i As Long
        For i = 0 To UBound(m_lngHeight)
            '繪圖代碼略
        Next
    End Sub

    類clsRows:
    Public RowIndex As Long

    Dim m_lngHeight() As Long
    Public Property Get Height() As Long
        Height = m_lngHeight(RowIndex)
    End Property
    Public Property Let Height(ByVal New_Value As Long)
        m_lngHeight(RowIndex) = New_Value
    End Property
    Friend Sub BindArray(ByRef v() As Long)
        m_lngHeight = v
    End Sub

    按我們的思路,當修改clsRows中的m_lngHeight()時,clsGrid中的m_lngHeight()應該一起改變,因爲clsRows中的m_lngHeight()是clsGrid中的m_lngHeight()一個引用。
    其實不然,在BindArray(ByRef v() As Long)中,只有v()是正確的引用,當使用m_lngHeight = v語句時,結果是m_lngHeight按照v構建了一個新的數組。
    那麼,怎麼實現clsRows中的m_lngHeight()是clsGrid中的引用呢?
    正確的辦法是將clsRows中的m_lngHeight這個安全數組的pvData成員指針指向clsGrid中的m_lngHeight中的pvData成員(具體可參才SAFEARRAY結構),爲此,我們可以使用CopyMemory進行構造,然而,由於SAFEARRAY結構體是變長的,其長度隨維數而變化,從而導致CopyMemory不知源數據的長度。其實,我們可以巧用VARIANT,因爲VARIANT結構體是定長的,這樣就可以實現通用的引用來代替全局變量,需要注意的是,不要忘了在類銷燬前還原VARIANT,修正後的clsRows代碼如下:
    類clsRows:
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

    Public RowIndex As Long

    Dim m_lngHeight As Variant
    Public Property Get Height() As Long
        Height = m_lngHeight(RowIndex)
    End Property
    Public Property Let Height(ByVal New_Value As Long)
        m_lngHeight(RowIndex) = New_Value
    End Property
    Friend Sub BindArray(ByRef v As Variant)
        CopyMemory m_lngHeight, v, 16 'VARIANT結構體長度爲16
    End Sub
    Private Sub Class_Terminate()
        Dim v As Variant
        CopyMemory m_lngHeight, v, 16 '還原m_lngHeight
    End Sub

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章