2008. 7. 18. 15:02

[ADO 2.8] HelloData Code

'BeginHelloData
Option Explicit

Dim m_oRecordset As ADODB.Recordset
Dim m_sConnStr As String
Dim m_flgPriceUpdated As Boolean

Private Sub cmdGetData_Click()
    GetData
    
    If Not m_oRecordset Is Nothing Then
        If m_oRecordset.State = adStateOpen Then
            ' Set the proper states for the buttons.
            cmdGetData.Enabled = False
            cmdExamineData.Enabled = True
        End If
    End If
End Sub

Private Sub cmdExamineData_Click()
    ExamineData
End Sub

Private Sub cmdEditData_Click()
    EditData
End Sub

Private Sub cmdUpdateData_Click()
    UpdateData
    
    ' Set the proper states for the buttons.
    cmdUpdateData.Enabled = False
End Sub

Private Sub GetData()
    On Error GoTo GetDataError
    
    Dim sSQL As String
    Dim oConnection1 As ADODB.Connection
    
    m_sConnStr = "Provider='SQLOLEDB';Data Source='MySqlServer';" & _
                "Initial Catalog='Northwind';Integrated Security='SSPI';"
                              
    ' Create and Open the Connection object.
    Set oConnection1 = New ADODB.Connection
    oConnection1.CursorLocation = adUseClient
    oConnection1.Open m_sConnStr
    
    sSQL = "SELECT ProductID, ProductName, CategoryID, UnitPrice " & _
             "FROM Products"
   
    ' Create and Open the Recordset object.
    Set m_oRecordset = New ADODB.Recordset
    m_oRecordset.Open sSQL, oConnection1, adOpenStatic, _
                        adLockBatchOptimistic, adCmdText
                        
    m_oRecordset.MarshalOptions = adMarshalModifiedOnly
        
    ' Disconnect the Recordset.
    Set m_oRecordset.ActiveConnection = Nothing
    oConnection1.Close
    Set oConnection1 = Nothing
        
    ' Bind Recordset to the DataGrid for display.
    Set grdDisplay1.DataSource = m_oRecordset
    
    Exit Sub
    
GetDataError:
    If Err <> 0 Then
        If oConnection1 Is Nothing Then
           HandleErrs "GetData", m_oRecordset.ActiveConnection
        Else
           HandleErrs "GetData", oConnection1
        End If
    End If
    
    If Not oConnection1 Is Nothing Then
        If oConnection1.State = adStateOpen Then oConnection1.Close
        Set oConnection1 = Nothing
    End If
End Sub

Private Sub ExamineData()
    On Err GoTo ExamineDataErr
    
    Dim iNumRecords As Integer
    Dim vBookmark As Variant
        
    iNumRecords = m_oRecordset.RecordCount
    
    DisplayMsg "There are " & CStr(iNumRecords) & _
                " records in the current Recordset."
    
    ' Loop through the Recordset and print the
    ' value of the AbsolutePosition property.
    DisplayMsg "****** Start AbsolutePosition Loop ******"
    
    Do While Not m_oRecordset.EOF
        ' Store the bookmark for the 3rd record,
        ' for demo purposes.
        If m_oRecordset.AbsolutePosition = 3 Then _
            vBookmark = m_oRecordset.Bookmark
         
        DisplayMsg m_oRecordset.AbsolutePosition
                 
        m_oRecordset.MoveNext
    Loop
     
    DisplayMsg "****** End AbsolutePosition Loop ******" & vbCrLf
    
    ' Use our bookmark to move back to 3rd record.
    m_oRecordset.Bookmark = vBookmark
    MsgBox vbCr & "Moved back to position " & _
            m_oRecordset.AbsolutePosition & " using bookmark.", , _
            "Hello Data"
                    
    ' Display meta-data about each field. See WalkFields() sub.
    Call WalkFields
    
    ' Apply a filter on the type field.
    MsgBox "Filtering on type field. (CategoryID=2)", _
            vbOKOnly, "Hello Data"
            
    m_oRecordset.Filter = "CategoryID=2"
    
    ' Set the proper states for the buttons.
    cmdExamineData.Enabled = False
    cmdEditData.Enabled = True

    Exit Sub
    
ExamineDataErr:
    HandleErrs "ExamineData", m_oRecordset.ActiveConnection
End Sub

Private Sub EditData()
    On Error GoTo EditDataErr
    
    'Recordset still filtered on CategoryID=2.
    'Increase price by 10% for filtered records.
    MsgBox "Increasing unit price by 10%" & vbCr & _
        "for all records with CategoryID = 2.", , "Hello Data"
    
    m_oRecordset.MoveFirst
    
    Dim cVal As Currency
    Do While Not m_oRecordset.EOF
        cVal = m_oRecordset.Fields("UnitPrice").Value
        m_oRecordset.Fields("UnitPrice").Value = (cVal * 1.1)
        m_oRecordset.MoveNext
    Loop
    
    ' Set the proper states for the buttons.
    cmdEditData.Enabled = False
    cmdUpdateData.Enabled = True

    Exit Sub
    
EditDataErr:
    HandleErrs "EditData", m_oRecordset.ActiveConnection
End Sub

Private Sub UpdateData()
    On Error GoTo UpdateDataErr
    
    Dim oConnection2 As New ADODB.Connection
    
    MsgBox "Removing Filter (adFilterNone).", , "Hello Data"
    m_oRecordset.Filter = adFilterNone
    
    Set grdDisplay1.DataSource = Nothing
    Set grdDisplay1.DataSource = m_oRecordset
    
    MsgBox "Applying Filter (adFilterPendingRecords).", , "Hello Data"
    m_oRecordset.Filter = adFilterPendingRecords
    
    Set grdDisplay1.DataSource = Nothing
    Set grdDisplay1.DataSource = m_oRecordset
    
    DisplayMsg "*** PRE-UpdateBatch values for 'UnitPrice' field. ***"
   
    ' Display Value, UnderlyingValue, and OriginalValue for
    ' type field in first record.
    If m_oRecordset.Supports(adMovePrevious) Then
        m_oRecordset.MoveFirst
        DisplayMsg "OriginalValue   = " & _
            m_oRecordset.Fields("UnitPrice").OriginalValue
        DisplayMsg "Value           = " & _
            m_oRecordset.Fields("UnitPrice").Value
    End If
        
    oConnection2.ConnectionString = m_sConnStr
    oConnection2.Open
     
    Set m_oRecordset.ActiveConnection = oConnection2
    m_oRecordset.UpdateBatch
    
    m_flgPriceUpdated = True
    
    DisplayMsg "*** POST-UpdateBatch values for 'UnitPrice' field ***"
   
    If m_oRecordset.Supports(adMovePrevious) Then
         m_oRecordset.MoveFirst
         DisplayMsg "OriginalValue   = " & _
             m_oRecordset.Fields("UnitPrice").OriginalValue
         DisplayMsg "Value           = " & _
             m_oRecordset.Fields("UnitPrice").Value
    End If
    
    MsgBox "See value comparisons in txtDisplay.", , _
           "Hello Data"
                 
    'Clean up
    oConnection2.Close
    Set oConnection2 = Nothing
    Exit Sub
    
UpdateDataErr:
    If Err <> 0 Then
        HandleErrs "UpdateData", oConnection2
    End If
   
    If Not oConnection2 Is Nothing Then
        If oConnection2.State = adStateOpen Then oConnection2.Close
        Set oConnection2 = Nothing
    End If
End Sub

Private Sub WalkFields()
    On Error GoTo WalkFieldsErr
    
    Dim iFldCnt As Integer
    Dim oFields As ADODB.Fields
    Dim oField As ADODB.Field
    Dim sMsg As String
    
    Set oFields = m_oRecordset.Fields
    
    DisplayMsg "****** BEGIN FIELDS WALK ******"
    
    For iFldCnt = 0 To (oFields.Count - 1)
        Set oField = oFields(iFldCnt)
        sMsg = ""
        sMsg = sMsg & oField.Name
        sMsg = sMsg & vbTab & "Type: " & GetTypeAsString(oField.Type)
        sMsg = sMsg & vbTab & "Defined Size: " & oField.DefinedSize
        sMsg = sMsg & vbTab & "Actual Size: " & oField.ActualSize
        
        grdDisplay1.SelStartCol = iFldCnt
        grdDisplay1.SelEndCol = iFldCnt
        DisplayMsg sMsg
        MsgBox sMsg, , "Hello Data"
    Next iFldCnt
    
    DisplayMsg "****** END FIELDS WALK ******" & vbCrLf
    
    'Clean up
    Set oField = Nothing
    Set oFields = Nothing
    Exit Sub
    
WalkFieldsErr:
    Set oField = Nothing
    Set oFields = Nothing
    
    If Err <> 0 Then
        MsgBox Err.Source & "-->" & Err.Description, , "Error"
    End If
End Sub

Private Function GetTypeAsString(dtType As ADODB.DataTypeEnum) As String
    ' To save space, we are only checking for data types
    ' that we know are present.
    Select Case dtType
        Case adChar
            GetTypeAsString = "adChar"
        Case adVarChar
            GetTypeAsString = "adVarChar"
        Case adVarWChar
            GetTypeAsString = "adVarWChar"
        Case adCurrency
            GetTypeAsString = "adCurrency"
        Case adInteger
            GetTypeAsString = "adInteger"
    End Select
End Function

Private Sub HandleErrs(sSource As String, ByRef m_oConnection As ADODB.Connection)
    DisplayMsg "ADO (OLE) ERROR IN " & sSource
    DisplayMsg vbTab & "Error: " & Err.Number
    DisplayMsg vbTab & "Description: " & Err.Description
    DisplayMsg vbTab & "Source: " & Err.Source
   
    If Not m_oConnection Is Nothing Then
        If m_oConnection.Errors.Count <> 0 Then
            DisplayMsg "PROVIDER ERROR"
            Dim oError1 As ADODB.Error
            For Each oError1 In m_oConnection.Errors
                DisplayMsg vbTab & "Error: " & oError1.Number
                DisplayMsg vbTab & "Description: " & oError1.Description
                DisplayMsg vbTab & "Source: " & oError1.Source
                DisplayMsg vbTab & "Native Error:" & oError1.NativeError
                DisplayMsg vbTab & "SQL State: " & oError1.SQLState
            Next oError1
            m_oConnection.Errors.Clear
            Set oError1 = Nothing
        End If
    End If
    
    MsgBox "Error(s) occurred. See txtDisplay1 for specific information.", , _
           "Hello Data"
   
    Err.Clear
End Sub

Private Sub DisplayMsg(sText As String)
    txtDisplay1.Text = (txtDisplay1.Text & vbCrLf & sText)
End Sub

Private Sub Form_Resize()
    grdDisplay1.Move 100, 700, Me.ScaleWidth - 200, (Me.ScaleHeight - 800) / 2
    txtDisplay1.Move 100, grdDisplay1.Top + grdDisplay1.Height + 100, _
                    Me.ScaleWidth - 200, (Me.ScaleHeight - 1000) / 2
End Sub

Private Sub Form_Load()
    cmdGetData.Enabled = True
    cmdExamineData.Enabled = False
    cmdEditData.Enabled = False
    cmdUpdateData.Enabled = False
    
    grdDisplay1.AllowAddNew = False
    grdDisplay1.AllowDelete = False
    grdDisplay1.AllowUpdate = False
    m_flgPriceUpdated = False
End Sub

Private Sub Form_Unload(Cancel As Integer)
    On Error GoTo ErrHandler:
    
    Dim oConnection3 As New ADODB.Connection
    Dim sSQL As String
    Dim lAffected As Long
   
    ' Undo the changes we've made to the database on the server.
    If m_flgPriceUpdated Then
        sSQL = "UPDATE Products SET UnitPrice=(UnitPrice/1.1) " & _
            "WHERE CategoryID=2"
        oConnection3.Open m_sConnStr
        oConnection3.Execute sSQL, lAffected, adCmdText
        
        MsgBox "Restored prices for " & CStr(lAffected) & _
            " records affected.", , "Hello Data"
    End If
           
    'Clean up
    oConnection3.Close
    Set oConnection3 = Nothing
    m_oRecordset.Close
    Set m_oRecordset = Nothing
    Exit Sub
    
ErrHandler:
    
    If Not oConnection3 Is Nothing Then
        If oConnection3.State = adStateOpen Then oConnection3.Close
        Set oConnection3 = Nothing
    End If
    If Not m_oRecordset Is Nothing Then
        If m_oRecordset.State = adStateOpen Then m_oRecordset.Close
        Set m_oRecordset = Nothing
    End If
End Sub

'EndHelloData


출처 : http://msdn.microsoft.com/en-us/library/ms810711.aspx

2008. 7. 15. 07:49

ActiveX와 Application에서 Mutex 문제


문제 : MainThread에서 Mutext생성 후 wait 계열 함수로 소유권을 요청하고, 메인쓰레드에서 ActiveX를 생성하면, ActiveX 내에서 다시 동기화를 위해 Mutex를 생성한 후 소유권을 요청하는 데, 이 때 ActiveX 역시 소유권을 가짐. 의도하는 바는 ActiveX랑 메인쓰레드랑 동기화를 하려고 했으나, 아래 예제에서는 동기화가 되지 않음.


아래 첨부를 해결하자! (2003 빌드)



원인 :

ActiveX를 생성하는 Main Thread랑 ActiveX 컨트롤이 같은 쓰레드이므로 Wait 계열 함수에서 항상 뮤텍스에 대한 소유권을 가짐

해결 :

MainThread에서 CreateThread 함수를 이용하여 쓰레드를 생성하고 그 쓰레드 내에서 뮤텍스를 생성하면 메인쓰레드에서 Wait 계열 함수로 소유권을 요청해도 소유권이 반환되지 않으므로 정상적인 동기화가 동작함.
2008. 7. 14. 23:36

비스타에서 Visual Studio 2003 빌드 시 regsvr32 등록 에러 문제

Re: call to DllRegisterServer failed with error code 0x80040200
Hi,

Open a command prompt by right clicking CMD and using 'run as
administrator', then try registering the library file from there.

--
Best of Luck,

Rick Rogers, aka "Nutcase" - Microsoft MVP
http://mvp.support.microsoft.com/
Windows help - www.rickrogers.org

"hbetancur" <hbetancur@discussions.microsoft.com> wrote in message
news:4CB6AF44-FC48-4C13-9323-E73EDE350824@microsoft.com...
> I'm trying to install manually a ActiveX control using the utility
> regsvr32.exe, but I got this error message. Can anyone give an idea what
> i'm
> doing wrong???


출처 : 구글 검색 후 링크 따라감...

관리자 권한으로 regsvr32 를 실행하면 해결 된다는 말.

더 좋은 방법을 찾아보자!
2008. 7. 13. 23:49

TN017: Destroying Window Objects

TN017: Destroying Window Objects

 

이 노트는 CWnd::PostNcDestory 멤버 함수의 사용을 설명한다. 만일 CWnd를 상속한 객체에 최적화된 할당을 만들려고 한다면 이 함수를 사용해라. 이 노트는 또한  아주 중요한 규칙에 대한 몇 가지 이유를 설명한다. C++ 윈도우 객체를 파괴(destroy)하기 위해서는, “delete” 하지 마라. 이것은 중요하다. 만약 아래의 가이드라인을 따른다면, Cleanup 문제를 거의 가지지 않을 것이다. (예를 들면, C++ 메모리를 delete/free 하는 것을 잊어먹거나, HWND 와 같은 시스템 리소스를 해제하는 것을 잊어먹거나, 너무 많이 객체를 해제한다거나)

 

The Problem

윈도우 객체(CWnd를 상속한 클래스의 객체)C++ 객체(애플리케이션의 힙에 할당된) HWND(윈도우 관리자에 의해 시스템 리소스 내에 할당된) 모두를 나타낸다. 윈도우 객체를 파괴하는 방법은 여러 가지가 있기 때문에, 시스템 리소스 또는 애플리케이션 메모리 누수를 방지하는 그리고 객체와 윈도우 핸들이 한번 이상 파괴되는 것을 방지하기 위한 어떤 규칙 세트를 제공해야만 한다.

이것은 메모리 관리 문제 보다 더 심각하다. 윈도우는 사용자-인터페이스를 가진다 : 화면에 그려지는 윈도우; 윈도우가 파괴되면, 시스템 리소스에도 영향이 있다. 애플리케이션 주소 공간 내에서 C++ 메모리 누수는 시스템 리소스가 누수 되는 것 만큼은 나쁘지 않다.

 

Destroying Windows

윈도우 객체를 파괴하는 두 가지 가능한 방법은 :

  • CWnd::DestoryWindow 를 호출하거나 윈도우 API ::DestoryWindow를 호출한다.
  • Delete 연산자를 사용하여 명시적으로 삭제 하기

 

첫 번째 경우는 지금까지 가장 일반적이다. 이 경우는 당신의 코드가 직접적으로

DestoryWindow를 호출하지 않더라도 적용된다. 사용자가 직접적으로 프레임 윈도우를 닫을 때(WM_CLOSE의 기본적인 행위는 DestoryWindow를 호출하는 것이다), 그리고 부모 윈도우가 파괴 될 때, 윈도우는 모든 자식에 대해 DestoryWindow를 호출한다.

두 번째 경우, 윈도우 객체에 대한 delete 연산자의 사용, 는 거의 사용되어서는 안되며 아래 소개된 경우에만 사용되어야 한다. (The second case, the use of the delete operator on Windows objects, should be very rare and only in the cases outlined below.)

 

Auto Cleanup with CWnd::PostNcDestroy

윈도우를 파괴할 때, 윈도우로 보내지는 마지막 윈도우 메시지는 WM_NCDESTORY 이다. 그 메시지에 대한 디폴트 CWnd 핸들러(CWnd::OnNcDestory)C++ 개체와 HWND를 분리할 것이다 그리고 가상 함수인 PostNcDestory를 호출할 것이다. 일부 클래스는 C++ 객체를 삭제하기 위해 이 함수를 재정의(override)한다.

CWnd::PostNcDestory의 디폴트 구현은 아무것도 하지 않는다 그것은 스택 프레임에 할당된 윈도우 객체 또는 다른 객체 내에 내장된(embedded) 윈도우 객체에 대해서는 적절하다. 힙에 할당되도록 디자인 된 (다른 C++ 객체 내에 내장되지 않은) 윈도우 객체에 대해서는 적절하지 않다.

The default implementation of CWnd::PostNcDestroy does nothing which is appropriate for window objects allocated on the stack frame or embedded in other objects. This is not appropriate for window objects that are designed to be allocated by themselves on the heap (not embedded in other C++ object).

 

힙에 할당되도록 디자인 된 클래스는 delete this를 수행하기 위해 PostNcDestory 멤버 함수를 재정의 한다. 이 문장은 C++ 객체와 연결된 모든 C++ 메모리를 해제할 것이다. 만일 m_hWnd NULL 이 아닌 경우 디폴트 CWnd 소멸자가 DestoryWindow를 호출하더라도, 핸들은 분리될 것이고, Cleanup 단계(phase)동안 NULL이 되기 때문에 이것은 무한 재귀(infinite recursion)로 빠지지는 않는다.

Note   CWnd::PostNcDestroy은 일반적으로 윈도우 파괴의 일부로서, WM_NCDESTORY 메시지가 처리된 후에 호출된다, 그리고 HWND C++ 윈도우 객체는 더 이상 붙여지지(attached) 않는다. CWnd::PostNcDestory 는 실패(failure)가 일어나면 대부분의 Create 호출의 구현 내에서 또한 호출될 것이다. (자동 Cleanup 규칙에 대해서는 아래를 보라)

 

Auto Cleanup Classes

아래 클래스는 auto-cleanup을 위해 디자인되지 않았다. 일반적으로 다른 C++ 객체 내에 내장되거나 스택 상에 존재한다:

  • All standard Windows controls (CStatic, CEdit, CListBox, and so on).
  • Any child windows derived directly from CWnd (for example, custom controls).
  • Splitter windows (CSplitterWnd).
  • Default control bars (classes derived from CControlBar, see Technical Note 31 for enabling auto-delete for control bar objects).
  • Dialogs (CDialog) designed for modal dialogs on the stack frame.
  • All the standard dialogs except CFindReplaceDialog.
  • The default dialogs created by ClassWizard.

아래 클래스는 auto-cleanup을 위해 디자인되어 있다. 일반적으로 힙에 할당된다.

  • Main frame windows (derived directly or indirectly from CFrameWnd).
  • View windows (derived directly or indirectly from CView).

만일 이러한 모든 규칙을 깨뜨리고 싶다면, 상속한 클래스 안에 PostNcDestory 멤버 함수를 재정의 해야만 한다. 클래스에 auto-cleanup을 추가하기 위해서는, 기본 클래스를 호출하고 그러고 나서 delete this 를 수행한다. Auto-cleanup을 클래스로부터 제거하기 위해서는, 기본 클래스내에 PostNcDestroy 함수 대신에 바로 CWnd::PonstNcDestroy 를 호출한다.

위에서 언급한 것에 대한 가장 일반적인 사용은 힙에 할당될 수 있는 modeless 다이얼로그를 생성하는 것이다.

 

When to Call 'delete'

윈도우 객체를 파괴하기 위한 권장되는 방법은 C++ 멤버 함수 또는 전역 ::DestroyWindow API 둘 중 하나, DestroyWindow를 호출하는 것이다.

MDI 자식 윈도우를 파괴하기 위해서는 전역 ::DestroyWindow API를 호출해서는 안된다. 대신에 가상 멤버 함수 CWnd::DestroyWindow 를 사용한다.

Auto-cleanup을 수행하지 않는 C++ 윈도우 객체의 경우, delete 대신에 DestroyWindow 를 사용하는 것은 CWnd::~CWnd 소멸자 내에서 VTBL 이 올바르게 상속된 클래스를 가리키고 있지 않는 DestoryWindow를 호출해야만 하는 문제를 피하게 한다. MFC의 진단 (디버그) 버전이 경고할 미묘한 버그가 이 경우에 만들어 질 수 있다.

 

Warning: calling DestroyWindow in CWnd::~CWnd

   OnDestroy or PostNcDestroy in derived class will not be called

 

Auto-cleanup을 수행하는 C++ 윈도우 객체의 경우, 반드시 DestroyWindow를 호출해야만 한다. 만약 delete 연산자를 직접적으로 사용한다면, MFC 진단 메모리 할당자(MFC diagnostic memory allocator)는 당신이 메모리를 두 번 해제하고 있다고 알려 줄 것이다 (PostNcDestroy auto-cleanup 구현 내에서 delete에 대한 첫 번째 호출뿐만 아니라 “delete this”에 의한 간접적인 호출).

 
출처 : MSDN 2003 (ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1042/vclib/html/_MFCNOTES_TN017.htm)

2008. 7. 9. 21:09

TN038: MFC/OLE IUnknown Implementation

IUnknown
OLE는 interface로서 IUnknown 으로부터 상속된 모든 클래스를 참조한다.
IUnknown "interface"는 구현이 되지 않은 추상 인터페이스만을 아래와 같이 정의하고 있다.

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};

Note : __stdcall 과 같은 필수 호출 규약은 생략하였음

COM은 객체들을 추적하는데 있어 참조 카운트 스키마를 사용한다. 하나의 객체는 C++ 내에서 절대로 직접적으로 참조 될 수 없다. 대신에, COM 객체는 항상 포인터를 통해서 참조된다.

소유자(Owner)가 객체를 사용을 완료했을 때, 그 객체를 해제하기 위해서는, 그 객체의 Release 멤버 함수가 호출 된다. (그와 반대로 전통적인 C++에서는 delete 연산자를 이용한다.)

참조 카운트 메커니즘은 단일 객체에 대해 다중 참조를 관리하게 해준다. AddRefRelease는 그 객체에 대한 참조 카운트를 관리한다. 객체는 자신의 참조 카운트가 0이 될 때가지 삭제되지 않는다.

아래 AddRefRelease의 간단한 구현 예이다.

ULONG CMyObj::AddRef()
{
    return ++m_dwRef;
}

ULONG CMyObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

QueryInterface는 동일한 객체에 대한 다른 인터페이스를 얻을 수 있도록 한다. 이 인터페이스들은 보통 IUnknown으로부터 상속되고 새로운 멤버 함수를 추가함으로서 부가적인 기능을 추가한다. COM 인터페이스는 인터페이스 내에 선언된 멤버 변수들을 절대로 소유하지 않으며, 모든 멤버 함수는 순수-가상 함수로 선언된다. 아래는 그 예이다.

class IPrintInterface : public IUnknown
{
public:
    virtual void PrintObject() = 0;
};

만일 IUnknown 개체만을 가지고 있는 경우에 IPrintInterface 을 얻기 위해서는 IPrintInterfaceIID를 사용하여 IUnknown::QueryInterface를 호출한다. IID는 인터페이스를 식별하는 고유의 128 비트 숫자이다. 각각의 인터페이스마다 IID가 존재한다. pUnk가 IUnknown 객체의 포인터라면, IPrintInterface 를 얻어오는 코드는 다음과 같다.

IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface,
    (void**)&pPrint) == NOERROR)
{
    pPrint->PrintObject();
    pPrint->Release();  
        // release pointer obtained via QueryInterface
}

객체에 IPrintInterface와 IUnknown 인터페이스 둘다를 지원하기 위해서는 어떻게 해야 할까?
이 경우에, IPrintInterface가 IUnknown을 직접적으로 상속하고 있기 때문에 아주 단순하다. IPrintInterface를 구현함으로써, IUnknown 은 자동적으로 지원된다. 예로 :

class CPrintObj : public CPrintInterface
{
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
    virtual ULONG AddRef();
    virtual ULONG Release();
    virtual void PrintObject();
};

AddRef와 Release의 구현은 위에서 구현된 코드와 같을 것이다. CPintObj::QueryInterface는 아래와 같다.

HRESULT CPrintObj::QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

보는바와 같이, Interface Identifier(IID)가 인식되면, 포인터가 개체에 대입된다. 성공적인 QueryInterface 는 암시적인 AddRef를 초래한다는 것을 기억하라.

물론, CEditObj::Print를 또한 구현해야 한다. IPrintInterface는 직접적으로 IUnknown 인터페이스로부터 상속되었기 때문에 간단하다. 그러나 만약 IUnknown 인터페이스로부터 상속된 두개의 다른 인터페이스를 지원하길 원한다면, 어떨지 고려해보자.

class IEditInterface : public IUnkown
{
public:
    virtual void EditObject() = 0;
};

C++ 다중 상속을 이용하는 것과 같이 IEditInterface와 IPrintInterface 모두를 지원하는 클래스를 구현하는 방법은 매우 많다. 이 문서는 이것을 구현하기 위한 중첩클래스(Nested class)를 사용하는 것에 집중할 것이다.

class CEditPrintObj
{
public:
    CEditPrintObj();

    HRESULT QueryInterface(REFIID iid, void**);
    ULONG AddRef();
    ULONG Release();
    DWORD m_dwRef;

    class CPrintObj : public IPrintInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_printObj;

    class CEditObj : public IEditInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual ULONG QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_editObj;
};

전체 구현은 아래와 같다.

CEditPrintObj::CEditPrintObj()
{
    m_editObj.m_pParent = this;
    m_printObj.m_pParent = this;
}

ULONG CEditPrintObj::AddRef()
{
    return ++m_dwRef;
}

CEditPrintObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

HRESULT CEditPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = &m_printObj;
        AddRef();
        return NOERROR;
    }
    else if (iid == IID_IEditInterface)
    {
        *ppvObj = &m_editObj;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

ULONG CEditPrintObj::CEditObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CEditObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CEditObj::QueryInterface(
    REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

ULONG CEditPrintObj::CPrintObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CPrintObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CPrintObj::QueryInterface(
    REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

대부분의 IUnknown 구현은 CEditPrintObj::CEditObj 와 CEditPrintObj::CPrintObj내에 코드를 중첩하는 것보다 CEditPrintObj 클래스에 위치시키는 것을 기억하라. 이것은 코드량을 줄여주고 버그를 피할 수 있게 한다. 여기서 핵심은 IUnknown 인터페이스로부터 그 객체가 지원하는 모든 인터페이스를 가져오게 하기 위해 QueryInterface를 호출하는 것이 가능하다는 것이다, 그리고 그러한 각각의 인터페이스로부터도 모든 인터페이스를 가져오는 것 또한 가능하다. 이것은 각각의 인터페이스로터 이용가능한 모든 QueryInerface 함수는 똑같이 동작한다는 것을 의미한다. 이러한 내장 객체들이 외부객체(Outer Object)에 있는 구현을 호출하기 위해서는, Back-Pointer가 사용된다(m_pParent). m_pParent 포인터는 CEditPrintObj 생성자에서 초기화된다.

이제 CEditPrintObj::CPrintObj::PrintObject와 CEditPrintObj::CEditObj::EditObject를 구현해야 한다. 한가지 기능을 추가하기 위해 약간의 코드가 더해졌다. 운이 좋게도, 인터페이스들이 한개의 멤버 함수만을 가지고 있는 경우는 매우 일반적이지 않으며 이러한 경우, EditObject와 PrintObject는 보통 한개의 인터페이스로 합쳐질 것이다.

이러한 단순한 시나리오의 경우에 해당하는 많은 설명과 코드가 있다. MFC/OLE 클래스는 더 심플한 대안을 제공한다. MFC 구현은 윈도우 메시지가 메시지 맵에 래핑되는것과 유사한 테크닉을 사용한다. 이러한 기능은 Interface Map이라 불리우고 다음 섹션에서 논의된다.

MFC Interface Maps

MFC/OLE 는 개념(concept)과 수행(execution)에 있어 MFC의 메시지 맵과 유사한 인터페이스 맵과 디스패치 맵의 구현을 제공한다. MFC 인터페이스 맵의 핵심 기능은 아래와 같다.

* CCmdTarget 클래스내에 구현된 IUnknown의 표준 구현
* AddRef 와 Release에 의해 수정되는 참조 카운트의 관리
* QueryInterface의 데이터 주도 구현(Data driven implementation)

게다가, 인터페이스 맵은 아래의 향상된 기능을 지원한다.

* 집합체가 될 수 있는 COM 개체(aggregatable COM ojbect)를 생성하는 것을 지원
* COM 객체의 구현 안에 집합체(aggregate object)를 사용하는 것을 지원
* 구현은 후킹가능하고 확장할 수 있다.

집합(aggregation)에 대한 자세한 정보는, OLE Programmer's Reference를 보라.

MFC의 인터페이스 맵 지원은 CCmdTarget 클래스 내에 내장되어 있다. CCmdTarget은 참조 카운트 뿐만 아니라 IUnknown 구현과 연결된 모든 멤버함수를 가진다 (예를 들어 참조 카운트는 CCmdTarget내에 있다). OLE COM을 지원하는 클래스를 생성하기 위해서는, CCmdTarget을 상속하고 의도하는 인터페이스를 구현하기 위한 다양한 매크로와 CCmdTarget의 멤버 함수를 사용한다. MFC의 구현은 위에서 설명한 예제와 같이 각각의 인터페이스 구현을 정의하기 위해 중첩 클래스를 사용한다. IUnknown의 표준 구현 뿐만 아니라 몇가지 반복적인 코드를 제거하는 여러 매크로를 이용하여 더 쉽게 구현할 수 있게 해준다

Inteface Map Basic

MFC 인터페이스 맵을 사용하는 클래스를 구현하기

1. 직접 혹은 간접적으로 CCmdTarget을 상속한다.
2. 클래스 정의에 DECLARE_INTERFACE_MAP 함수를 사용한다.
3. 지원하고자 하는 각각의 인터페이스에 대해, 클래스 정의 내에 BEGIN_INTERFACE_PARTEND_INTERFACE_PART 매크로를 사용한다.
4. 구현 파일 내에, 클래싀 인터페이스 맵을 정의하기 위해 BEGTIN_INTERFACE_MAPEND_INTERFACE_MAP 매크로를 사용한다.
5. 지원된 각각의 IID에 대해, IID를 클래스의 특정 부분에 맵핑하기 위해 BEGIN_INTERFACE_MAPEND_INTERFACE_MAP 매크로 사이에 INTERFACE_PART 매크로를 사용한다.
6. 지원하는 인터페이스를 나타내는 각각의 중첩 클래스를 구현한다.
7. 부모, CCmdTarget을 상속한 객체에 접근하기 위해 METHOD_PROLOGUE를 사용한다.
8. AddRef, Release, 그리고 QueryInterface는 이와 같은 함수의 CCmdTaret 구현 (ExternalAddRef, ExternalRelease, 그리고 ExternalQueryInterface)에 위임할 수 있다.

위의 CPrintEditObj 예제는 아래와 같이 구현될 수 있다.

class CPrintEditObj : public CCmdTarget
{
public:
    // member data and member functions for CPrintEditObj go here

// Interface Maps
protected:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(EditObj, IEditInterface)
        STDMETHOD_(void, EditObject)();
    END_INTERFACE_PART(EditObj)

    BEGIN_INTERFACE_PART(PrintObj, IPrintInterface)
        STDMETHOD_(void, PrintObject)();
    END_INTERFACE_PART(PrintObj)
};

위의 선언은 CCmdTarget을 상속한 클래스를 생성한다. DECLARE_INTERFACE_MAP 매크로는 프레임워크에게 이 클래스는 커스텀 인터페이스 맵을 가질것이다라는 것을 알려준다. 게다가, BEGIN_INTERFACE_PARTEND_INTERFACE_PART 매크로는 CEditObj와 CPrintObj로 이름지어진 중첩 클래스를 정의한다. (X는 "C"로 시작하는 전역 클래스와 "I"로 시작하는 인터페이스 클래스와 중첩 클래스를 구분하기 위해서만 사용된다.) 이 클래스들에 대한 두개의 중첩 멤버(nested member)가 생성된다 : m_CEditObj 와 m_CPrintObj. 매크로는 자동적으로 AddRef, Release, 그리고 QueryInterface 함수를 선언한다. 그리하여 이 인터페이스 고유의 함수들만 선언한다. (OLE 매크로 STDMETHOD는 대상 플랫폼에 적합한 _stdcall 과 virtual 키워드를 제공하기 위해 사용된다.)

이 클래스의 경우 인터페이스를 정의하기 위해서는

BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
    INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
    INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()

이것은 각각 m_CPrintObj와 IID_IPrintInterface를, m_CEditObj와 IID_IEditInterface를 연결한다. QueryInterface의 CCmdTarget 구현(CCmdTarget::ExternalQueryInterface)는 m_CPrintObj와 m_CEditObj에 대한 포인터를 반환하기 위해 이 맵을 사용한다. IID_IUnknown에 대한 엔트리를 포함하는 것은 불필요하다. 프레임워크는 IID_IUnknown이 요구되었을 때 맵에 있는 첫번째 인터페이스(이 경우에, m_CPrintObj)를 사용할 것이다.

비록 BEGIN_INTERFACE_PART 매크로가 자동적으로 AddRef, Release 그리고 QueryInterface 함수를 선언했다고 할지라도, 여전히 그것들을 구현해야할 필요가 있다.

ULONG FAR EXPORT CEditPrintObj::XEditObj::AddRef()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CEditPrintObj::XEditObj::Release()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CEditPrintObj::XEditObj::QueryInterface(
    REFIID iid, void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

void FAR EXPORT CEditPrintObj::XEditObj::EditObject()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    // code to "Edit" the object, whatever that means...
}

CEditPrintObj::CPrintObj에 대한 구현은 위의 CEditPrintObj::CEditObj의 정의와 비슷할 것이다. 비록 이러한 함수들을 자동적으로 생성하기 위해 사용될 수 있는 매크로를 만들수 있을지라도, 매크로가 한줄 이상의 코드를 생성할 때 브레이크 포인터를 걸기가 어려워 질 것이다. 이러한 이유로, 이 코드는 수동으로 확장된다.

메시지 맵의 프레임워크 구현을 사용함으로써, 다음과 같은 작업을 할 필요가 없다.

* QueryInterface 구현
* AddRef와 Release 구현
* 두 인터페이스에 대한 각각의 built-in 메소드 선언

게다가, 프레임워크는 내부적으로 메시지 맵을 사용한다. 이것은 이미 특정 인터페이스를 지원하는 그리고 그 인터페이스에 대한 대체(replacement) 또는 추가(addition)를 제공하는 COleServerDoc와 같은 프레임워크 클래스를 얻어 올 수 있게 한다. 이것은 프레임워크가 베이스 클래스로부터 인터페이스 맵을 상속하는 것을 전적으로 지원한다는 사실에서 가능해진다 - 즉 BEGIN_INTERFACE_MAP이 두번째 파라미터로 베이스 클래스의 이름을 가지는 이유이다.

Aggregation and Interface Maps

stand-along COM 개체를 지원하는 것에 외에, MFC는 또한 집합(aggregation)을 지원한다. 집합은 여기서 다루기에는 너무 복잡하다. 집합에 대한 자세한 정보는 OLE Programmer's Reference를 참조해라. 이 문서는 간단하게 프레임워크와 인터페이스 맵 내에 집합을 구축하는 것에 대해 설명할 것이다.

집합을 사용하는 방법에는 두가지가 있다.

(1) 집합을 지원하는 COM 객체를 사용하는 것
(2) 다른 객체에 의해 집합될 수 있는(can be aggregated) 객체를 구현하는 것

이러한 기능은 "집합체 사용하기(Using an aggregate object)"와 "집합될 수 있는 객체 만들기(Making an object aggregatable)" 로 다루어 진다. MFC는 두가지 모두 지원한다.

Using an aggregate object

집합체를 사용하기 위해서는, 그 집합체를 QueryInterface 매커니즘 내에 묶는 몇가지 방법이 필요한다. 다른 말로 하면, 집합체는 객체의 고유한 일부(native part)인 것처럼 동작해야 한다. 그래서 이것을 어떻게 MFC의 인터페이스 맵 매커니즘으로 묶을수 있을까? INTERFACE_PART 매크로외에, CCmdTarget 을 상속한 클래스의 일부로 집합체를 선언할 수 있다. 그렇게 하기 위해, INTERFACE_AGGREGATE 매크로가 사용된다. 이것은 멤버 변수( IUnknown이나 상속클래스의 포인터이어야 한다)를 지정할 수 있게 한다 그리고 이것은 인터페이스 맵 매커니즘 안으로 통합된다. CCmdTarget::ExternalQueryInterface가 호출될 때 포인터가 NULL이 아니고,  그리고 요청된 IID가 CCmdTarget 개체 자신에 의해 지원되는 고유의 IID가 아니라면 프레임워크는 자동적으로 집합체의 QueryInterface 멤버 함수를 호출할 것이다, .

INTERFACE_AGGREGATE 매크로 사용하기

1. 집합체에 대한 포인터를 포함할 멤버변수(IUnknown 포인터)를 선언한다
2. 인터페이스 맵내에 INTERFACE_AGGREGATE를 포함한다. 이것은 멤버변수를 이름으로 참조한다.
3. 특정 지점(보통 CCmdTarget::OnCreateAggreagets 내에서), 멤버 변수는 NULL이 아닌 다른것으로 초기화 한다.

아래는 예:

class CAggrExample : public CCmdTarget
{
public:
    CAggrExample();

protected:
    LPUNKNOWN m_lpAggrInner;
    virtual BOOL OnCreateAggregates();

    DECLARE_INTERFACE_MAP()
    // "native" interface part macros may be used here
};

CAggrExample::CAggrExample()
{
    m_lpAggrInner = NULL;
}

BOOL CAggrExample::OnCreateAggregates()
{
    // wire up aggregate with correct controlling unknown
    m_lpAggrInner = CoCreateInstance(CLSID_Example,
        GetControllingUnknown(), CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);
    if (m_lpAggrInner == NULL)
        return FALSE;
    // optionally, create other aggregate objects here
    return TRUE;
}

BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
    // native "INTERFACE_PART" entries go here
    INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()

m_lpAggrInnter 은 생성자에서 NULL로 초기화 된다. 프레임워크는 QueryInterface의 디폴트 구현안에서 NULL 멤버 변수는 무시할 것이다. OnCreateAggregates는 집합체를 실질적으로 생성하기 위한 좋은 위치이다. 만일 COleObjectFactory의 MFC 구현 외부에서 객체를 생성하고 있다면, 명시적으로 그것을 호출해야만 할 것이다. CCmdTarget::OnCreateAggregates내에서 집합체를 생성하는 이유뿐만 아니라 CCmdTarget::GetControllingUnknown 의 사용은 집합체를 생성하는 것에 대해 논의될 때 명백해 질 것이다.

이 기법은 객체에 집합체가 지원하는 모든 인터페이스를  고유의 인터페이스에 추가해 줄 것이다. 집합체가 지원하는 인터페이스 일부만을 원한다면, CCmdTarget::GetInterfaceHook 을 재정의 할 수 있다. 이것은 QueryInterface와 유사하게, 아주 낮은 레벨의 hookability를 제공한다. 보통, 집합체가 지원하는 모든 인터페이스를 원할 것이다.

Making an object Implemtation Aggregatable

집합될 수 있는 객체의 경우, AddRef, Release, 그리고 QueryInterface의 구현은 "Controlling Unknown"에 위임해야만 한다. 다른 말로 하면, 객체가 다른 객체의 일부가 되기 위해서는, IUnknown을 상속한 다른 객체에 AddRef, Release, 그리고 QueryInterface를 위임해야만 한다. 이 "controlling unknown"는 객체가 생성될 때 그 객체에 제공된다, 즉, COleObjectFactory의 구현에 제공된다. 이것을 구현하는 것은 약간의 오버헤드를 수반하며 어떤 경우에는 바람직하지 않을수 있다. 그래서 MFC는 이것을 선택할 수 있게 만들었다. 객체가 집합가능하게 만들기 위해서는, 객체의 생성자에서 CCmdTarget::EnableAggregation을 호출해야 한다.

그 객체가 또한 집합체를 사용한다면, 그 집합체에다가 올바른 "controlling unknown"을 전달해야만 한다. 보통 이 IUnknown 포인터는 집합체가 생성될 때 객체에 전달된다. 예를 들면, pUnkOuter 파라미터는 CoCreateInstance로 생성된 객체들을 위한 "controlling unknown"이다. 올바른 "controlling unknown" 포인터는 CCmdTarget::GetControllingUnknown을 호출함으로써 얻을 수 있다. 그러나 그 함수에 의해 반환되는 값은 생성자에서는 유효하지 않다. 이 이유로, GetControllingUnknown 의 반환값이 신뢰할 수 있는, 비록 COleObjectFactory 구현으로부터 생성된 것조차, CCmdTarget::OnCreateAggreates 를 재정의하여 집합체를 생성하도록 권유된다.

인위적인 참조 카운트를 증가시키거나 해제할때 객체가 올바른 참조 카운트를 조절하는 것 또한 매우 중요하다. 이것을 보장하기 위해, InternalReleaseInternalAddRef 대신에 ExternalAddRefExternalRelease를 항상 호출해라. 집합체를 지원하는 클래스에서 InternalRelease 또는 InternalAddRef를 호출하는 것은 거의 볼 수 없다.



참고 : MSDN 2003 Technical Note #38

2008. 7. 7. 00:49

About Mouse

Mouse Cursor
A single-pixel point called the hot spot, a point that the system tracks and recognizes as the position of the cursor.

The window need not be active or have the keyboard focus to receive a mouse message.

You can use the SystemParametersInfo function with the SPI_GETMOUSE or SPI_SETMOUSE flag to retrieve or set mous speed.

Mouse Capture
An application can chage this behavior by using the SetCapture function to route mouse messages to a specific window. The window receives all mouse messages until the application calls the ReleaseCapture function or specifies another capture window, or until the user clicks a window created by another thread.

When the mouse capture changes, the system sends a WM_CAPTUECHANGED message to the window that is losing the mouse capture.

GetCapture
To determine whether one of its window has captured the mouse

Mouse ClickLock
This feature lets a user lock down the primary mouse button after a single click.

Mouse Configuration
An application can determine whether the system includes a mouse by passing the SM_MOUSEPRESENT value to the GetSystemMetrics function

Mouse Messages
The system converts mouse input events into messages and posts them to the appropriate thread's message queue. When mouse message are posted faster than a thread can process them, the system discards all but the most recent mouse message.

Client Area Mouse Messages
An application can call the TrackMouseEvent function to have the system send two other messages. It posts the WM_MOUSEHOVER message when the cursor hovers over the client area for a certain time period. It posts the WM_MOUSELEAVE message when the cursor leaves the client area.

Message Parameters
lParam
Indicates the position of the cursor hot spot. The low-order word indicates the x-coordinate of the hot spot, and the high-order word indicates the y-coordinate. The coordinates are specified in client coordinates.

wParam
Indicates the status of the other mouse buttons and the CTRL and SHIFT keys at the time of the mouse event.
The wParam parameter can be a combination of the following values.
Value Meaning
MK_CONTROL The CTRL key is down.
MK_LBUTTON The left mouse button is down.
MK_MBUTTON The middle mouse button is down.
MK_RBUTTON The right mouse button is down.
MK_SHIFT The SHIFT key is down.
MK_XBUTTON1 Windows 2000/Windows XP: the first X button is down.
MK_XBUTTON2 Windows 2000/Windows XP: the second X button is down.


Double-Click Messages
The system generates a double-click message when the user clicks a mouse button twice in quick succession.
When the user clicks a button, the system establishes a rectangle centered around the cursor hot spot. It also marks the time at which the click occurred.
When the user clicks the same button a second time, the system determines whether the hot spot is still within the rectangle and calculates the time elapsed since the first click.
If the hot spot is still within the rectangle and the elapsed time does not exceed the double-click time-out value, the system generates a double-click message.

An application can get and set double-click time-out values by using the GetDoubleClickTime and SetDoubleClickTime functions,
Alternatively the application can set the double-click-time-out-value by using the SPI_SETDOUBLECLICKTIME flag with the SystemParametersInfo function. It can also set te size of the rectangle that the system uses to detect double clicks by passing the SPI_SETDOUBLECLKWIDTH and SPI_SETDOUBLECLKHEIGHT flags to SystemParametersInfo.
Note, however, that setting the double-click-time-out vlaue and rectangle affects all applications.

An application-defined window does not, by default, receive double-click messages. Because of the system overhead involved in generating double-click messages, these messages are gnerated only for windows belonging to classes that have the CS_DBLCLKS class style.

A double-click message is always the third message in a four-message series.
For example, double-clicking the left mouse button generates the following message sequences:

1. WM_LBUTTONDOWN
2. WM_LBUTTONUP
3. WM_LBUTTONDBLCLK
4. WM_LBUTTONUP

Because a window always receives a button-down message before receiving a double-click message, an application typically uses a double-click message to extend a task it began during a button-down message.

Nonclient Area Mouse Messages
A window's nonclient area consists of its border, menu bar, title bar, scroll bar, window menu, minimize button, and maximize button.

A window must pass nonclient area mouse message to the DefWindowProc function to take advantage of the built-in mouse interface.

There is a corresponding nonclient area mouse message for each client area mouse message.

lParam
Unlike coordinates of client area mouse messages, the coordinaes are specified in screen coordinates rather than client coordinates.

wParam
contains a hit-test value, a value that indicates where in the nonclient area the mouse event occurred.

The WM_NCHITTEST Message
Whenever a mouse event occurs, the system sends a WM_NCHITTEST message to either the window that contains the cursor hot spot or the window that has captured the mouse. The system uses this message to determine whether to send a client area or nonclient area mouse message.
An application that must receive mouse movement and mouse button messages must pass the WM_NCHITTEST message to the DefWindowProc function.

The lParam parameter of the WM_NCHITTEST message contains the screen coordinates of the cursor hot spot. The DefWindowProc function examines the coordinates and returns a hit-test value that indicates the location of the hot spot. Te hit-test value can be one of the following values.

Value Location of hot spot
HTBORDER                                        In the border of a window that does not have a sizing border.
HTBOTTOM In the lower-horizontal border of a window.
HTBOTTOMLEFT In the lower-left corner of a window border.
HTBOTTOMRIGHT In the lower-right corner of a window border.
HTCAPTION In a title bar.
HTCLIENT In a client area.
HTCLOSE In a Close button.
HTERROR On the screen background or on a dividing line between windows (same as HTNOWHERE, except that the DefWindowProc function produces a system beep to indicate an error).
HTGROWBOX In a size box (same as HTSIZE).
HTHELP In a Help button.
HTHSCROLL In a horizontal scroll bar.
HTLEFT In the left border of a window.
HTMENU In a menu.
HTMAXBUTTON In a Maximize button.
HTMINBUTTON In a Minimize button.
HTNOWHERE On the screen background or on a dividing line between windows.
HTREDUCE In a Minimize button.
HTRIGHT In the right border of a window.
HTSIZE In a size box (same as HTGROWBOX).
HTSYSMENU In a System menu or in a Close button in a child window.
HTTOP In the upper-horizontal border of a window.
HTTOPLEFT In the upper-left corner of a window border.
HTTOPRIGHT In the upper-right corner of a window border.
HTTRANSPARENT In a window currently covered by another window in the same thread.
HTVSCROLL In the vertical scroll bar.
HTZOOM In a Maximize button.

Mouse Sonar
This feature briefly shows several concentric circles around the pointer when the user presses and releases the CTRL key.

SystemPrametersInfo
SPI_GETMOUSESONAR
SPI_SETMOUSESONAR

Mouse Vanish
This feature hides the pointer when the user is typing. The mouse pointer reappears when the user moves the mouse.

SstemParametersInfo
SPI_GETMOUSEVANISH
SPI_SETMOUSEVANISH

The Mouse Wheel
When an application processes the WM_MOUSEWHEEL message, it must return zero. When an application processes the MSH_MOUSEWHEEL message, it must return TRUE.

Detecting a Mouse with a Wheel
To determine if a mouse with a wheel is connected, call GetSystemMetrics(SM_MOUSEWHEELPRESENT)

ClipCursor()
To confine the cursor to the client area during the line drawing operation.

Processing a Double Click Message
To receive double-click messages, a window must belong to a window class that has the CS_DBLCLKS class style.

2008. 7. 6. 23:59

About Keyboard

Keyboard Input Model

Scan Code
Assigned to each key on a keyboard s a unique value
a device-dependent identifier for the key on the keyboard

Virtual-Key-Code
The keyboard device driver interprets a scan code and translates (maps) it to a virtual-key-code,
a device-independent value defined by the system that identifies the purpose of a key

After translating a scan code, the key board layout creates a message that includes the scan code, the virtual-key code, and other information about the keystroke, and then places the message in the system message queue.

The system removes the message from the system message queue and posts it to the message queue of the appropriate thread.
Eventually, the trhread's message loop removes the message and passes it to the appropriate window procedure for processing.

The following figure illustrates the keyboard input model :

사용자 삽입 이미지

Keyboard Focus and Activation

GetFocus()
determine which of its windows (if any) currently has the keyboard focus

SetFocus()
A thread can give the keybord focus to one of its windows by calling this.

The concept of keyboard focus is related to that of the active window.

Active Window
The active window is the top-level window the user is currently working with.

SetActiveWindow()
A thread can activate a top-level window by using this

GetActiveWindow()
Determine whether a top-level window it created is active by using this

When one window is deactivated and another activated, the system sends the WM_ACTIVE message.
The low-order word of the wParam parameter is zero,
if the window is being deactivated and nonzero if it is being activated.

BlockInput() & SendInput()
To block keyboard and mouse input events from reaching applications, use BlockInput.
Note, the BlockInput function will not interfere with the asynchronous keyboard input-state table.
This means that calling the SendInput function wihle input is blocked will change the asynchronous keyboard input-state table.

Keystroke Messages
WM_KEYDOWN
WM_SYSKEYDOWN
WM_KEYUP
WM_SYSKEYUP

System and Nonsystem Keystrokes
The system makes a distinction between system keystrokes and nonsystem keystrokes.

If your window procedure must process a system keystroke message, make sure that after processing the message the procedure passes it to the DefWindowProc function.

System Keystroke messages are primarily for use by the system rather than by an application.

Nonsystem Keystroke message are for use by application windows; the DefWindowProc function does nothing with them.

Virtual-Key codes Described
The wParam parameter of a keystroke message contains the virtual-key code of the key that was pressed or released.

Keystroke Message Flags
The lParam parameter of a keystroke message contains additional information about the keystroke that generated the message. This information includes the repeat count, the scan code, the extended-key flag, the context code, the previous key-state flag, and the transition-state flag.

사용자 삽입 이미지

An applicatoin can use the following values to manipulate the keystroke flags.
-----------------------------------------------------------------------------------------
KF_ALTDOWN Manipulates the ALT key flag, which indicated if the ALT key is pressed.
KF_DLGMODE Manipulates the dialog mode flag, which indicates whether a dialog box is active.
KF_EXTENDED Manipulates the extended key flag.
KF_MENUMODE Manipulates the menu mode flag, which indicates whether a menu is active.
KF_REPEAT Manipulates the repeat count.
KF_UP Manipulates the transition state flag.
-----------------------------------------------------------------------------------------

Repeat Count
Determine whether a keystroke message represents more than one keystroke.
Instead of filling the system message queue with the resulting key-down messages, the system combines the messages into a single key down message and increments the repeat count.

Scan Code
The scan code is the value that the keyboard hardware generates when the user presses a key.
a device dependent value

Extended-Key Flag
Indicates whether the keystroke message originated from one of the additional keys on the enhanced keyboard.
The extended keys consist of :
  the ALT and CTRL keys on the right-hand side of the keyboard;
  the INS, DEL, HOME, END, PAGE UP, PAGE DOWN,
  And arrow keys in the cluster to the left of the numeric keypad;
  the NUM LOCK key; the BRAEK(CTRL+PAUSE) key;
  the PRINT SCRN key; and divide(/) and ENTER keys in the numeric keypad.

The extended-key flag is set if the key is an extended key.

Context Code
Indicates whether the ALT key was down when the keystroke message was generated.
The code is 1 if the ALT key was down and 0 if it was up.

Previous Key-State Flag
Indicates whether the key that generated the keystroke message was previously up or down.
It is 1 if the key was previously down and 0 if the key was previously up.
You can use this flag to identify keystroke messages generated by the keyboard's automatic repeat feature.

Transition-State Flag
Indicates whether pressing a key or releasing a key generated the keystroke message.
This flag is always set to 0 for WM_KEYDOWN and WM_SYSKEYDOWN messages;
it is always set to 1 for WM_KEUP and WM_SYSKEYUP messages.

Character Messages
To retrieve character codes, an application must include the TranslateMessage function in its thread message loop. TranslateMessage passes a WM_KEYDOWN or WM_SYSKEDOWN message to the keyboard layout. The layout examines the message's virtual-key code and, if it corresponds to a character key, provides the character code equivalent (taking into account the state of the SHIFT and CAPS LOCK keys).

Nontsystem Character Messages
The TranslateMessage function generates a WM_CHAR or WM_DEADCHAR message when it processes a WM_KEYDOWN message. Similarly, it generates a WM_SYSCHAR or WM_SYSDEADCHAR message when it processes a WM_SYSKEYDOWN message.

Note the WM_CHAR uses 16-bit Unicode transformation format (UTF) while WM_UNICHAR uses UTF-32.

The wParam parameter of all character messages contains the character code of the character key that was pressed.

The contents of the lParam parameter of a character message are identical to the contents of the lParam parameter of the key-down message that was translated to produce the character message.

Dead-Character Messages
Some non-English keyboards contain character keys that are not expected to produce characters by themselves. Instead, they are used to add a diacritic to the character produced by the subsequent keystroke. These keys are called dead keys.

The window with the keyboard focus would receive the following sequence of messages:

1. WM_KEYDOWN
2. WM_DEADCHAR
3. WM_KEYUP
4. WM_KEYDOWN
5. WM_CHAR
6. WM_KEYUP

TranslateMessage generates the WM_DEADCHAR message when it processes the WM_KEYDOWN message from a dead key. If the subsequent keystroke generates a character that cannot be combined with a diacritic, the system generates two WM_CHAR mesages.

The TranslateMessage function generates the WM_SYSDEADCHAR message when it processes the WM_SYSKEYDOWN message from a system dead key (a dead key that is pressed in combination with the LT ke)

Key Status
The application can use the GetKeyState function to determine the status of a virtual key at the time the current message was generated; it can use the GetAsyncKeyState function to retrieve the current status of a virtual key.

An application can retrieve the name of any key from the device driver by calling the GetKeyNameText function.

Keystroke and Character Translations
The system includes several special purpose functions that translate scan codes, character codes, and virtual-key codes provided by various keystroke message. These functions include MapVirtualKey, ToAscii, ToUnicode and VkKeyScan.

Hot-Key Support
A hot key is a key combination that generates a WM_HOTKEY message, a message the system places at the top of a thread's message queue, bypassing any existing messages in the queue.

For example, by defining a hot key consisting of the CTRL+C key combination, an application can allow the user to cancel a lengthy operation.

To define a hot key, an application calls the RegisterHotKey function.

Before the application terminates, it should use the UnregisterHotKey function to destroy the hot key.

Applications can use a hot key control to make it easy for the user to choose a hot key. Hot key controls are typicall used to define a hot key that activates a window;

Simulating Input
To simulate an uninterruped series of user input events, use the SendInput function.

The SendInput function works by injecting a series of simulated input events into a device's input stream. The effect is similar to calling the keybd_event or mouse_event function repeatedly, except that the system ensures that no other input events intermingle with the simulated events.

If the return value is zero, then input was blocked.

The SendInput function does not reset the keyboard's current state. Therefore, if the user has any keys pressed when you call this function, they might interfere with the events that this function generates. If you are concerned about possible interference, check the keyboard's state with the GetAsyncKeyState function and correct as necessary.

Translating Character Messages

MSG msg;
BOOL bRet;

while (( bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0) 
{
    if (bRet == -1);
    {
        // handle the error and possibly exit
    }
    else
    { 
        if (TranslateAccelerator(hwndMain, haccl, &msg) == 0) 
        { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 
}

2008. 7. 5. 23:22

Windows (창) 개체

MSDN 2003 에서 Windows 창 개체 관련 정리



키워드 정리:
CFrameWnd; CMDIFrameWnd; CMDIChildWnd; Registerclass; AfxRegisterWndClass; OnClose; DestoryWindow; OnNcDestory; PostNcDestory; Detach; CWindowDC; CMetaFileDC; CClientDC; CPen; CBrush; CFont; CBitmatp; CPalette; CRgn; CImage; DeviceContext; PreCreateWindow; FWS_ADDTOTITLE; CDocTemplate; MDICLIENT: OnActivateView; ON_UPDATE_COMMAND_UI; DragAcceptFiles; LoadStdProfileSettings; DDE 응답; RegisterShellFileTypes; EnableShellOpen;
2008. 7. 5. 23:17

CObject 관련 정리

CObject 관련한 MSDN 2003 문서 정리



키워드:
Serialization; DECLARE_DYNAMIC; DECLARE_DYNCREATE; DECLARE_SERIAL; IsKindOf; CRuntimeClass; DumpallObjectsSince; CDumpContext; AssertValid; ASSERT_VALID;
2008. 7. 5. 23:01

About Windows

윈도우에 대해서 기본적인 지식을 제공함

MSDN 2003 정리 자료 (영문)

키워드 정리 :
DesktopWindow; Client Area; Nonclient Area; GetDesktopWindow; SystemParametersInfo; SetWindowLong;
CreateWindowEx; GetWindowLong; WindowFromPoint; ChildWindowFromPoint; ChildWindowFromPointEx;
GetSystemMetrics; SM_CXMIN; SM_CYMIN; AdjustWindowRect; AdjustWindowRectEx; EnumWindows;
HWND; FindWindow; FindWindowEx; IsWindow; HWND_BROADCAST; HWND_DESKTOP; MapWindowPoints; CreateMutex; GetLastError; SW_SHOWDEFAULT; IsWindowUnicode; RegisterClass;
WM_NCCREATE; CREATESTRUCT; WM_PARENTNOTIFY; WM_CREATE; EnumThreadWindows;
GetWindowThreadProcessId; ShowWindowAsync; WS_CLIPSIBLING, WS_CLIPCHILDREN;  SetParent; GetParent; Descendant Window; IsChild; EnumChildWindows; Layered Window; WS_EX_TRANSPARENT; HWND_MESSAGE; Foreground Window; Background Window; GetForegroundWindow; SetForegroundWindow; LockSetForegroundWindow; AllowSetForegroundWindow; BroadcastSystemMessage; Owned Window; GW_OWNER; Z-Order; topmost window; WS_EX_TOPMOST; BringWindowtoTop; GetTopWindow; DeferWindowPos; GetTopWindow; GetNextWindow; SetActiveWindow; GetActiveWdinow; Active Window; WM_ACTIVEAPP, WM_ACTIVE; Disabled Window; EnableWindow; WM_ENABLE; IsEnabledWindow; Hidden Window; WM_SHOWWINDOW; IsWindowVisibile; ShowOwnedPopups; ArrangeIconicWindows; OpenIcon; ShowWindow; SetWindowPlacement; IsZoomed; IsIconic; GetWindowPlacement; WM_QUERYOPEN; WM_GETMINMAXINFO; WS_THICKFRAME; WM_SYSCOMMAND; ScreenToClient; MapWindowPoints; GetClientRect; CascadeWindow; tileWindow; WM_WINDOWPOSCHANGING; WM_WINDOWPOSCHANGED; WM_NCCALCSIZE; WM_CLOSE;


출처 : ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1042/winui/winui/windowsuserinterface/windowing/windows/aboutwindows.htm