CWindow Release Candidate 29

Started by José Roca, July 01, 2017, 04:51:09 PM

Previous topic - Next topic

aloberr

on the other hand :
I tested your classes, it is not to criticize, but for example class CVAR, although Powerbasic uses assign and others ToUbyte etc...
island seems to me that it is better to have the constructor, the operator let and cast, in this way this CVAR will be confused with the VARIANT one. 
It is what DELPHI does and which also made successes of VB.
if CVAR inherits VARIANT, it is an opinion, good if you want to copy PowerBasic, it is also an option. 

José Roca

#46
The first version used LET and CAST and ended being a mess because if you pass 123 the compiler can't guess if it is a BYTE, a LONG, etc. Therefore, you have to use CBYTE, CLNG, etc., for numeric types, and, worst of all, you can't assign a variable by reference to make a VT_BYREF variant because if you pass a pointer to a variable, the compiler does not know if it is a pointer to a LONG, a DOUBLE or whatever.

PowerBasic let's you to optionally specify the kind of value that it is going to be assigned, e.g. DIM v AS VARIANT = 123 AS LONG, or DIM v AS VARIANT = BYREF variable, that is what I have done in the CVAR constructor and on the Assign method. I would have liked to do it in LET and CAST, but they don't allow me to pass the variant type.

One thing is to implement it natively in the compiler and another to do it using a class. Classes have its limitations.

Visual Basic was build to use COM Automation exclusively. It loads the type libraries to know which kind of variables expects the parameters of the method and coerces them. As a downside, you can't use low-level IUnknwon interfaces with VB. Some VBer's tried to workaround this limitation writing type libraries for these IUnknwon interfaces, but the results were no perfect because of differences in the calling conventions. Being a low-level pogrammer, i soon realized that it was not a language for me since the first time that I tried it. It certainly was very popular, but I don't like it.

As it seems that FreeBasic will never support BSTR, VARIANT and SAFEARRAY as native data types, I'm doing what I can to add support for them, although not in the VB way.

Paul Squires

Quote from: Jose Roca on July 10, 2017, 06:53:02 AM
Hi Paul,

The above compile problem is undetected by the WinFBE editor. It is checking for "ERROR!", "LINKING FAILED:", ""LINKING:" and "WARNING", but in my failed compile what can be found is "error: '_ZTSN14AFX_IDISPATCH_E' undeclared here (not in a function)" and "compiling C failed: 'C:\Users\Pepe\FreeBasic64\bin\win64\gcc.exe' terminated with exit code 1".


FreeBASIC Compiler - Version 1.05.0 (01-31-2016), built for win64 (64bit)
Copyright (C) 2004-2016 The FreeBASIC development team.
standalone
target:       win64, x86-64, 64bit
compiling:    C:\Users\Pepe\FreeBasic64\AfxTests\CDispInvoke\WMI_Test_02.bas -o C:\Users\Pepe\FreeBasic64\AfxTests\CDispInvoke\WMI_Test_02.c (main module)
compiling C:  C:\Users\Pepe\FreeBasic64\bin\win64\gcc.exe -m64 -march=x86-64 -S -nostdlib -nostdinc -Wall -Wno-unused-label -Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable -Wno-main -Werror-implicit-function-declaration -O0 -fno-strict-aliasing -frounding-math -fno-math-errno -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -masm=intel "C:\Users\Pepe\FreeBasic64\AfxTests\CDispInvoke\WMI_Test_02.c" -o "C:\Users\Pepe\FreeBasic64\AfxTests\CDispInvoke\WMI_Test_02.asm"
C:\Users\Pepe\FreeBasic64\AfxTests\CDispInvoke\WMI_Test_02.c:2178:115: error: '_ZTSN14AFX_IDISPATCH_E' undeclared here (not in a function)
static struct $8fb_RTTI$ _ZTSN3AFX19AFX_ISWBEMDATETIME_E = { (void*)0ull, (uint8*)"N3AFX19AFX_ISWBEMDATETIME_E", &_ZTSN14AFX_IDISPATCH_E };
                                                                                                                   ^
compiling C failed: 'C:\Users\Pepe\FreeBasic64\bin\win64\gcc.exe' terminated with exit code 1


So you should also check for "ERROR:" and for "COMPILING C FAILED:".


This has now been added. I'll update GitHub before going to bed.
Paul Squires
PlanetSquires Software

José Roca

Using sometimes "error!:" and others "error:" is what I call lack of consistency.

Paul Squires

I think the "error!" version is returned from the gorc resource compiler.
Paul Squires
PlanetSquires Software

José Roca

#50
Quote from: aloberr on July 13, 2017, 08:05:42 PM
jose wrote
QuoteIt could have been if FB had not the nasty habit of passing an address to optional BYREF parameters. In PowerASIC I can check with VARPTR if the parameter has been omitted, e.g. IF VARPTR(x) = 0 THEN..., but with FB not because VARPTR(x) will always return a value <> 0. As in many of the methods I need to know if the parameter is null, I have used the same syntax in all of them. It would have been baffling to have to use one syntax with some methods and another with others.
why do you need optionnal parameter where byval can do the job , for example on the drawline procedure CgPen is the class and the first parameter and can not be optional, if it is not assign the procedure used value initialised in the constructor of the class.

Besides DrawLine, there are more than 600 other methods, many of which have parameters that are optional. If the parameter is declared, for example, as BYREF pImageAttributes AS CGpImageAttributes, instead of BYVAL pImageAttributes AS CGpImageAttributes PTR = NULL,  how can I make it optional and how can I pass a null pointer?

aloberr

jose wrote
QuotePowerBasic let's you to optionally specify the kind of value that it is going to be assigned, e.g. DIM v AS VARIANT = 123 AS LONG, or DIM v AS VARIANT = BYREF variable, that is what I have done in the CVAR constructor and on the Assign method. I would have liked to do it in LET and CAST, but they down allow me to pass the variant type.
It is done by Freebasic using;
dim v as CVAR=cast(LONG,123)
vcvar=cast(UBYTE,onevar)
because Powerbasic VARIANT is not Windows VARAINT and Powerbasic VARIANT can be compare with CVAR or OLEVARIANT

aloberr

I had the same difficulties in passing the variables by referance to OLEVARIANT, I surmounted that and my class olevariant lengthened considerably, people do not like the  big executables size.   And yet the DELPHI which is a very powerful language and fastest of the market produces theses big exe . 

José Roca

Instead, I'm using

DIM cv AS CVAR = CVAR(123, "LONG")

that is not much different and not more difficult to use.

IMO there is nothing wrong in using PowerBASIC techniques. After all, I was a PB programmer and this framework is mainly targeted to PB programmers. I once posted an small example in the PB forum (the first and only time that I have done that) to help a PB programmer and I was quickly reprimanded by a certain Mr. Swiss, a self appointed guardian of the orthodoxy. Apparently I must not use .inc files because some deprecated IDEs for PB don't work well with them, and, instead, I must use modules and libraries, losing the dead code removal feature and waste my time building libraries.

José Roca

#54
Quote from: aloberr on July 14, 2017, 06:22:48 PM
I had the same difficulties in passing the variables by referance to OLEVARIANT, I surmounted that and my class olevariant lengthened considerably, people do not like the  big executables size.   And yet the DELPHI which is a very powerful language and fastest of the market produces theses big exe . 

For VT_BYREF variants, I have this method:


' ========================================================================================
' Assigns a value by reference (a pointer to a variable).
' ========================================================================================
PRIVATE FUNCTION CVar.AssignRef (BYVAL _pvar AS ANY PTR, BYVAL _vType AS WORD) AS HRESULT
   CVAR_DP("CVAR AssignRef")
   VariantClear(@vd)
   IF _pvar = NULL THEN RETURN E_INVALIDARG
   SELECT CASE _vType
      CASE VT_BOOL      : vd.vt = VT_BOOL OR VT_BYREF      : vd.pboolVal = _pvar
      CASE VT_I1        : vd.vt = VT_I1 OR VT_BYREF        : vd.pcVal = _pvar
      CASE VT_UI1       : vd.vt = VT_UI1 OR VT_BYREF       : vd.pbVal = _pvar
      CASE VT_I2        : vd.vt = VT_I2 OR VT_BYREF        : vd.piVal = _pvar
      CASE VT_UI2       : vd.vt = VT_UI2 OR VT_BYREF       : vd.puiVal = _pvar
      CASE VT_INT       : vd.vt = VT_INT OR VT_BYREF       : vd.pintVal = _pvar
      CASE VT_UINT      : vd.vt = VT_UINT OR VT_BYREF      : vd.puintVal = _pvar
      CASE VT_I4        : vd.vt = VT_I4 OR VT_BYREF        : vd.plVal = _pvar
      CASE VT_UI4       : vd.vt = VT_UI4 OR VT_BYREF       : vd.pulVal = _pvar
      CASE VT_I8        : vd.vt = VT_I8 OR VT_BYREF        : vd.pllVal = _pvar
      CASE VT_UI8       : vd.vt = VT_UI8 OR VT_BYREF       : vd.pullVal = _pvar
      CASE VT_R4        : vd.vt = VT_R4 OR VT_BYREF        : vd.pfltVal = _pvar
      CASE VT_R8        : vd.vt = VT_R8 OR VT_BYREF        : vd.pdblVal = _pvar
      CASE VT_BSTR      : vd.vt = VT_BSTR OR VT_BYREF      : vd.pbstrVal = _pvar
      CASE VT_UNKNOWN   : vd.vt = VT_UNKNOWN OR VT_BYREF   : vd.ppunkVal = _pvar
      CASE VT_DISPATCH  : vd.vt = VT_DISPATCH OR VT_BYREF  : vd.ppdispVal = _pvar
      CASE VT_DECIMAL   : vd.vt = VT_DECIMAL OR VT_BYREF   : vd.pdecVal = _pvar
      CASE VT_CY        : vd.vt = VT_CY OR VT_BYREF        : vd.pcyVal = _pvar
      CASE VT_DATE      : vd.vt = VT_DATE OR VT_BYREF      : vd.pdate = _pvar
      CASE VT_VARIANT   : vd.vt = VT_VARIANT OR VT_BYREF   : vd.pvarVal = _pvar
      CASE VT_SAFEARRAY : vd.vt = VT_SAFEARRAY OR VT_BYREF : vd.pvarVal = _pvar
      CASE VT_ERROR     : vd.vt = VT_ERROR OR VT_BYREF     : vd.pparray = _pvar
      CASE ELSE         : RETURN E_INVALIDARG
   END SELECT
END FUNCTION
' ========================================================================================


I will add another one that will use a string for the data type, easier to remember that these VT_xxx constants.

José Roca

Quote
And yet the DELPHI which is a very powerful language and fastest of the market produces theses big exe.

And even a compiler such FreePascal is mainly being used with the Lazarus framework, that produces bloated executables because of its goal to imitate Delphi.

My framework produces small executables, thanks to dead code removal. Only using pure SDK will you get smaller ones, but at the cost of losing the goodies that my famework offers.

Some may object that I'm using PB techniques instead of VB or Delphi techniques, but as I said I was a PB programmer, not a VB or Delphi one. I don't even know what Delphi does because I never have used it.


José Roca

#56
And now, back to coding. I almost have finished the WMI class. Files attached to this post.

I have implemented three ways of retrieving the information.

The first one is the classic one of executing a query and retrieve the results using an enumerator.


#include "windows.bi"
#include "Afx/CWmiDisp.inc"
using Afx

' // Connect to WMI using a moniker
' // Note: $ is used to avoid the pedantic warning of the compiler about escape characters
DIM pServices AS CWmiServices = $"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"
IF pServices.ServicesPtr = NULL THEN END

' // Execute a query
DIM hr AS HRESULT = pServices.ExecQuery("SELECT Caption, SerialNumber FROM Win32_BIOS")
IF hr <> S_OK THEN PRINT AfxWmiGetErrorCodeText(hr) : SLEEP : END

' // Get the number of objects retrieved
DIM nCount AS LONG = pServices.ObjectsCount
print "Count: ", nCount
IF nCount = 0 THEN PRINT "No objects found" : SLEEP : END

' // Enumerate the objects using the standard IEnumVARIANT enumerator (NextObject method)
' // and retrieve the properties using the CDispInvoke class.
DIM pDispServ AS CDispInvoke = pServices.NextObject
IF pDispServ.DispPtr THEN
   PRINT "Caption: "; pDispServ.Get("Caption").ToStr
   PRINT "Serial number: "; pDispServ.Get("SerialNumber").ToStr
END IF

PRINT
PRINT "Press any key..."
SLEEP


If the query returns more than one object, then we will use a loop:


#include "windows.bi"
#include "Afx/CWmiDisp.inc"
using Afx

' // Connect to WMI using a moniker
' // Note: $ is used to avoid the pedantic warning of the compiler about escape characters
DIM pServices AS CWmiServices = $"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"
IF pServices.ServicesPtr = NULL THEN END

' // Execute a query
DIM hr AS HRESULT = pServices.ExecQuery("SELECT * FROM Win32_Printer")
IF hr <> S_OK THEN PRINT AfxWmiGetErrorCodeText(hr) : SLEEP : END

' // Get the number of objects retrieved
DIM nCount AS LONG = pServices.ObjectsCount
print "Count: ", nCount
IF nCount = 0 THEN PRINT "No objects found" : SLEEP : END

' // Enumerate the objects
FOR i AS LONG = 0 TO nCount - 1
   PRINT "--- Index " & STR(i) & " ---"
   DIM pDispServ AS CDispInvoke = pServices.NextObject
   IF pDispServ.DispPtr THEN
      PRINT "Caption: "; pDispServ.Get("Caption").ToStr
      PRINT "Capabilities "; pDispServ.Get("Capabilities").ToStr
   END IF
NEXT

PRINT
PRINT "Press any key..."
SLEEP


José Roca

#57
The second way is to call the GetNamedProperties method after executing the query. GetNamedProperties generates a named collection of properties. This has the advantage of not having to use CDispInvoke.


#include "windows.bi"
#include "Afx/CWmiDisp.inc"
using Afx

' // Connect to WMI using a moniker
' // Note: $ is used to avoid the pedantic warning of the compiler about escape characters
DIM pServices AS CWmiServices = $"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"
IF pServices.ServicesPtr = NULL THEN END

' // Execute a query
DIM hr AS HRESULT = pServices.ExecQuery("SELECT Caption, SerialNumber FROM Win32_BIOS")
IF hr <> S_OK THEN PRINT AfxWmiGetErrorCodeText(hr) : SLEEP : END

' // Get the number of objects retrieved
DIM nCount AS LONG = pServices.ObjectsCount
print "Number of objects: ", nCount
IF nCount = 0 THEN PRINT "No objects found" : SLEEP : END

' // Get a collection of named properties
IF pServices.GetNamedProperties <> S_OK THEN PRINT "Failed to get the named properties" : SLEEP : END

' // Retrieve the value of the properties
'DIM cv AS CVAR = pServices.PropValue("Caption")
'PRINT cv.ToStr
PRINT pServices.PropValue("Caption").ToStr
PRINT pServices.PropValue("SerialNumber").ToStr

PRINT
PRINT "Press any key..."
SLEEP


Using a loop


#include "windows.bi"
#include "Afx/CWmiDisp.inc"
using Afx

' // Connect to WMI using a moniker
' // Note: $ is used to avoid the pedantic warning of the compiler about escape characters
DIM pServices AS CWmiServices = $"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"
IF pServices.ServicesPtr = NULL THEN END

' // Execute a query
DIM hr AS HRESULT = pServices.ExecQuery("SELECT * FROM Win32_Printer")
IF hr <> S_OK THEN PRINT AfxWmiGetErrorCodeText(hr) : SLEEP : END

' // Get the number of objects retrieved
DIM nCount AS LONG = pServices.ObjectsCount
print "Number of objects: ", nCount
IF nCount = 0 THEN PRINT "No objects found" : SLEEP : END

' // Enumerate the objects
FOR i AS LONG = 0 TO nCount - 1
   PRINT "--- Index " & STR(i) & " ---"
   ' // Get a collection of named properties
   IF pServices.GetNamedProperties(i) = S_OK THEN
      PRINT pServices.PropValue("Caption").ToStr
      PRINT pServices.PropValue("Capabilities").ToStr
   END IF
NEXT

PRINT
PRINT "Press any key..."
SLEEP


José Roca

#58
The third way is to use the Get method. It retrieves an object, that is either a class definition or an instance, based on the specified object path. Using this object, it generates a named collection of properties.


#include "windows.bi"
#include "Afx/CWmiDisp.inc"
using Afx

' // Connect to WMI using a moniker
DIM pServices AS CWmiServices = $"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"
IF pServices.ServicesPtr = NULL THEN END

' // Get an instance of the printer "OKI B410" --> change me
DIM hr AS HRESULT = pServices.Get("Win32_Printer.DeviceID='OKI B410'")
IF hr <> S_OK THEN PRINT AfxWmiGetErrorCodeText(hr) : SLEEP : END

' // Number of properties
PRINT "Number of properties: ", pServices.PropsCount
PRINT

' // Display some properties
PRINT "Port name: "; pServices.PropValue("PortName").ToStr
PRINT "Attributes: "; pServices.PropValue("Attributes").ToStr
PRINT "Paper sizes supported: "; pServices.PropValue("PaperSizesSupported").ToStr

PRINT
PRINT "Press any key..."
SLEEP


Another example:


#include "windows.bi"
#include "Afx/CWmiDisp.inc"
using Afx

' // Connect to WMI using a moniker
'DIM pServices AS CWmiServices = $"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"
' Use the constructor for server connection, just for trying...
DIM pServices AS CWmiServices = CWmiServices(".", "root\cimv2")
IF pServices.ServicesPtr = NULL THEN END

'// Get an instance of a file --> change me
DIM cbsPath AS CBSTR = ExePath & "\EX_CWMI_Get_02.bas"   ' --> change me
DIM hr AS HRESULT = pServices.Get("CIM_DataFile.Name='" & cbsPath & "'")
IF hr <> S_OK THEN PRINT AfxWmiGetErrorCodeText(hr) : SLEEP : END

' // Number of properties
PRINT "Number of properties: ", pServices.PropsCount
PRINT

' // Display some properties
PRINT "Relative path: "; pServices.PropValue("Path").ToStr
PRINT "FileName: "; pServices.PropValue("FileName").ToStr
PRINT "Extension: "; pServices.PropValue("Extension").ToStr
PRINT "Size: "; pServices.PropValue("Filesize").ToStr
'PRINT pServices.PropValue("LastModified").ToStr
PRINT "Date last modified: "; pServices.WmiDateToStr(pServices.PropValue("LastModified"), "dd-MM-yyyy")   ' // change the mask if needed

PRINT
PRINT "Press any key..."
SLEEP


TODO: Implement support to execute methods.

José Roca

The helper function AfxWmiGetErrorCodeText returns a descriptive and localized description of WMI errors.

The methods WmiDateToStr and WmiTimeToStr convert WMI date times to formatted strings using a mask, and WmiTimeToFileTime converts it to a FILETIME structure.

Also notice that one of the constructors allow to connect to remote servers (untested because I don't have access to a server)-


' ========================================================================================
' Connects to the namespace that is specified on the cbsNamespace parameter on the computer
' that is specified in the cbsServer parameter. The target computer can be either local or
' remote, but it must have WMI installed.
' - cbsServer:
'   Computer name to which you are connecting. If the remote computer is in a different domain
'   than the user account under which you log in, then use the fully qualified computer name.
'   If you do not provide this parameter, the call defaults to the local computer.
'      Example: server1.network.fabrikam
'   You also can use an IP address in this parameter. If the IP address is in IPv6 format,
'   the target computer must be running IPv6. An address in IPv4 looks like 111.222.333.444
'   An IP address in IPv6 format looks like 2010:836B:4179::836B:4179
' - cbsNamespace:
'   String that specifies the namespace to which you log on. For example, to log on to the
'   root\default namespace, use root\default. If you do not specify this parameter, it
'   defaults to the namespace that is configured as the default namespace for scripting.
'      Example: DIM pServices AS CWmiServices = CWmiServices(".", "root\cimv2")
'      where "." is a shortcut for the local computer.
' - cbsUser [in, optional]
'   User name to use to connect. The string can be in the form of either a user name or a
'   Domain\Username. Leave this parameter blank to use the current security context. The
'   cbsUser parameter should only be used with connections to remote WMI servers. If you
'   attempt to specify strUser for a local WMI connection, the connection attempt fails.
'   If Kerberos authentication is in use, then the username and password that is specified
'   in cbsUser and cbsPassword cannot be intercepted on a network. You can use the UPN
'   format to specify the cbsUser.
'      Example: "DomainName\UserName"
'   Note: If a domain is specified in cbsAuthority, then the domain must not be specified
'   here. Specifying the domain in both parameters results in an Invalid Parameter error.
' - cbsPassword [in, optional]
'   String that specifies the password to use when attempting to connect. Leave the
'   parameter blank to use the current security context. The cbsPassword parameter should
'   only be used with connections to remote WMI servers. If you attempt to specify
'   cbsPassword for a local WMI connection, the connection attempt fails. If Kerberos
'   authentication is in use then the username and password that is specified in cbsUser
'   and cbsPassword cannot be intercepted on the network.
' - cbsLocale [in, optional]
'   String that specifies the localization code. If you want to use the current locale,
'   leave it blank. If not blank, this parameter must be a string that indicates the
'   desired locale where information must be retrieved. For Microsoft locale identifiers,
'   the format of the string is "MS_xxxx", where xxxx is a string in the hexadecimal form
'   that indicates the LCID. For example, American English would appear as "MS_409".
' - cbsAuthority [in, optional]
'   ""
'      This parameter is optional. However, if it is specified, only Kerberos or NTLMDomain
'      can be used.
'   Kerberos:
'      If the cbsAuthority parameter begins with the string "Kerberos:", then Kerberos
'      authentication is used and this parameter should contain a Kerberos principal name.
'      The Kerberos principal name is specified as Kerberos:domain, such as Kerberos:fabrikam
'      where fabrikam is the server to which you are attempting to connect.
'         Example: "Kerberos:DOMAIN"
'   NTLMDomain:
'      To use NT Lan Manager (NTLM) authentication, you must specify it as NTLMDomain:domain,
'      such as NTLMDomain:fabrikam where fabrikam is the name of the domain.
'         Example: "NTLMDomain:DOMAIN"
'      If you leave this parameter blank, the operating system negotiates with COM to
'      determine whether NTLM or Kerberos authentication is used. This parameter should
'      only be used with connections to remote WMI servers. If you attempt to set the
'      authority for a local WMI connection, the connection attempt fails.
'      Note: If the domain is specified in cbsUser, which is the preferred location, then
'     it must not be specified here. Specifying the domain in both parameters results in
'     an Invalid Parameter error.
' - iSecurityFlags [in, optional]
'     Used to pass flag values to ConnectServer.
'     0 (0x0)
'        A value of 0 for this parameter causes the call to ConnectServer to return only
'        after the connection to the server is established. This could cause your program
'        to stop responding indefinitely if the connection cannot be established.
'    wbemConnectFlagUseMaxWait (128 (0x80))
'        The ConnectServer call is guaranteed to return in 2 minutes or less. Use this flag
'        to prevent your program from ceasing to respond indefinitely if the connection
'        cannot be established.
' If successful, WMI returns an SWbemServices object that is bound to the namespace that
' is specified in cbsNamespace on the computer that is specified in cbsServer.
' Usage example with the local computer:
'    DIM pServices AS CWmiServices = CWmiServices(".", "root\cimv2")
'    IF pServices.DispPtr = NULL THEN END
' Remarks
'   The ConnectServer method is often used when connecting to an account with a different
'   username and passwordâ€"credentialsâ€"on a remote computer because you cannot specify a
'   different password in a moniker string.
'   Using an IPv4 address to connect to a remote server may result in unexpected behavior.
'   The likely cause is stale DNS entries in your environment. In these circumstances, the
'   stale PTR entry for the machine will be used, with unpredictable results. To avoid
'   this behavior, you can append a period (".") to the IP address before calling
'   ConnectServer. This causes the reverse DNS lookup to fail, but may allow the
'   ConnectServer call to succeed on the correct machine.
' ========================================================================================
PRIVATE CONSTRUCTOR CWmiServices (BYREF cbsServer AS CBSTR, BYREF cbsNamespace AS CBSTR, _
   BYREF cbsUser AS CBSTR = "", BYREF cbsPassword AS CBSTR = "", BYREF cbsLocale AS CBSTR = "", _
   BYREF cbsAuthority AS CBSTR = "", BYVAL iSecurityFlags AS LONG = wbemConnectFlagUseMaxWait)

   ' // Initialize the COM library
   DIM hr AS HRESULT = CoInitialize(NULL)
   IF hr = S_OK OR hr = S_FALSE THEN m_bUninitCOM = TRUE
   ' // Connect to the server
   DIM pLocator AS Afx_ISWbemLocator PTR = AfxNewCom("WbemScripting.SWbemLocator")
   IF pLocator THEN m_Result = pLocator->ConnectServer(cbsServer, cbsNamespace, cbsUser, cbsPassword, _
      cbsLocale, cbsAuthority, iSecurityFlags, NULL, @m_pServices)
   pLocator->Release

END CONSTRUCTOR
' ========================================================================================


Working with WMI is now very easy!