Jump to content

Recommended Posts

Posted

@ptrex you are right on reflection but the base for that is system.type and system.activator.

see https://www.codeproject.com/Articles/55710/Reflection-in-NET that has some nice visualization of this
to get base types created you need createinstance of activator class as the other createinstance from assembly class do not support the core/base types like system.int32

within mscorlib.idl you then can read the definition of system.activator and potentially this leads then to

a Global $tIID__Activator ,  Global $tag_Activator = $tagIUnknown & _ which can be used for the objcreateinterface but i hope there is an easier way to have system.type and system.activator around. this $tag AIO stuff is powerfull but so far a lot of work (although AIO Wrapper generator helps in this area).

Just for reference if AIO experts are around :sweating: certainly not my native area of doing this in AutoIt

'~IID__Activator: TGUID = '{03973551-57A1-3900-A2B5-9083E3FF2943}';
'~CLASS_Activator: TGUID = '{9BA4FD4E-2BC2-31A0-B721-D17ABA5B12C3}';
  
Global $tIID__Activator = _AutoItObject_CLSIDFromString("{03973551-57A1-3900-A2B5-9083E3FF2943}")
Global $tagIUnknown = _
    "QueryInterface long(" & $refGUID & ";ptr);" & _
    "AddRef uint();" & _
    "Release uint();"
Global $tag_Activator = $tagIUnknown & _
    "GetTypeInfoCount long(uint*);" & _
    "GetTypeInfo long(uint;uint;int64);" & _
    "GetIDsOfNames long(" & $refObject & ";int64;uint;uint;int64);" & _
    "Invoke long(uint;" & $refObject & ";uint;short;int64;int64;int64;int64);"

; -------------------------------
Local $pActivator
; Initialize interface pointer $pActivator here, for example:
; _AutoItObject_CoCreateInstance(DllStructGetPtr($tCLSID_CActivator), 0, 1, DllStructGetPtr($tIID__Activator), $pActivator)
Local $objActivator = _AutoItObject_WrapperCreate($pActivator, $tag_Activator)

 

Posted

OK looks a different road indeed...?

Question  now is, should we not wrap up (don'the get confused with unwrap like I did ;)). And go public to the wide audience in the Example forum.

We have gone a long way already now that we access some native .Net classes.

Once in the open we might reach more .NETers, who can help in going the last mile.

In the meantime I will try out some more examples ....

Larsj / Dannyfirex / Trancexx your input is welcome...

Anyhow it is getting more and more fascinating each step we take :)

Posted

The only things that are not complete given below but a lot of power is already shown so with removing of consolewrites in clr.au3 there can be a good start

After rereading the thread (leaving out a lot of interesting references/studying material) this is a new summary

  1. Fix the CRL_CreateObject to be CLR_CreateObject :) check if clr_createinstance would be more logical naming
  2. Completeness of clr.au3 compared to mscorlib.idl 
  3. What do we need from mscoree.dll / idl
  4. How far you want to go from unmanaged AutoIt
  5. Native types seems to need system.type and system.activator and .net 4.7 seems to go that way from unmanaged with _activator interface
    https://msdn.microsoft.com/en-us/library/system.runtime.interopservices._activator(v=vs.80).aspx                                              2.0
    https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices._activator?view=netframework-4.7                      4,7
  6. Cleanup of clr.au3 (no debugging messages)
  7. Combine the examples see also point 14 below
    - Especially calling a AutoIt function from a managed class
    - Struggling with structures and enums in the forms example with things like point      all controls and drawings need an x,y point or size
     see post 39 for C++ example
    form.controls.add
  8. Summarize all links / documents references in a "logical" order
  9. Some understanding questions (maybe area in Wiki) like
    How to instantiate in AutoIt these hidden interfaces of mscorlib?
    Should they be wrapped in a .NET managed class or are they usable from AutoIt objCreateInterface in unmanaged code
    something like done for delphi https://raw.githubusercontent.com/outchy/jcl/master/jcl/source/windows/mscorlib_TLB.pas
    [
          odl,
          uuid(29DC56CF-B981-3432-97C8-3680AB6D862D),
          hidden,
          dual,
          oleautomation,
          custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Environment")    
    
        ]
        interface _Environment : IDispatch {
        };
        
            [
          uuid(9BA4FD4E-2BC2-31A0-B721-D17ABA5B12C3),
          version(1.0),
          noncreatable,
          custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Activator")
        ]
        coclass Activator {
            interface _Object;
            [default] interface _Activator;
        };
        
        [
          odl,
          uuid(0F240708-629A-31AB-94A5-2BB476FE1783),
          hidden,
          dual,
          oleautomation,
          custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Random")    
    
        ]
        interface _Random : IDispatch {
  10. document this on clsid as part of interfaces
    "GetInterface hresult(clsid;clsid;ptr*);"
  11.  
  12. Early and late bound activation in this thread should be getting a little clearer
        google on: COM_Interop_and_PInvoke.doc
        .NET applications can bind to COM classes in an early or late bound fashion. Early binding is a much more natural way of using a class.
         Early bound instances are typically created with the new operator and members are accessed directly through the object instance. 
         When early binding isn’t possible (or desirable) COM classes can also be created in a late bound fashion  
          COM classes that only support late binding through IDispatch are only accessible through reflection from within managed code. 

  13. Overview of which classes are objCreate instantiatable and which ones need createInstance logic (and maybe even when objCreateInterface comes around)

  14.  Combine  examples in post 25 + 28 + 29 + 56 + 59 + 60 + 62 + 65 + 68 CLR.ZIP + examples + 79 + 80 + 88 
    91 probably incorrect example
    102 + 108 +110  eventhandler with forms
    116 summary of larsj
    129 fixing of multiple parameters in CRL_CreateObject
    139 2 safearray functions
    140 new CLR.7z
    141 example stringbuilder
    147 "System.IO.FileInfo"
    149   Local $oFont = _CRL_CreateObject( $oDrawing, "System.Drawing.Font", "Courier New", 20 )
    150 extended CS class to prove multi parameter method call is working
    posts 140, 141 and 156 show the unwrap method using the CreateInstance_3 method
    159   Local $oDirectoryInfo = _CRL_CreateObject( $oAssembly, "System.IO.DirectoryInfo", $Dir & "\Downloads" )
     

 

Posted

Hi Junkew, 

Very good summary ! 

it covers most of the things except 2 other I would add to the list :

1. The current CLR.au3 still has an error in the last version which was corrected in one of the threads by Larsj,

     But never made it into the CLR.au3 : See Post 88

2. Define in the Summary what is NOT working or NOT Tested yet :

    - GUI Controls : not working 100 %

    - Native  .NET Functions returning an Array Object, not tested ...

    - ... Please add things you know are missing ...

 

So giving this long list, I guess we are not ready yet to release to the public yet ...

Best is the prepare this in this thread here and then move over to the Examples Section of the Forum ...

Posted (edited)

@All

This version covers...

CLR Cleanup :

1. Fix the CRL_CreateObject to be CLR_CreateObject : Done (- Name change I will up to you guys, I don't mind either way)

6. Cleanup of clr.au3 (no debugging messages) : Done

  • Including the correction of the error in post 88

7. Combine the examples see also point 14 below,

14 Example : Done 

Questions and Documentation: To Do

 

 

 

CLRv1.zip

Edited by ptrex
Posted

Nice.

Unfortunately a new not working example which I thought would be an easy one for the clipboard

Example()  ;~Not working
;~ maybe: You must avoid the ThreadStateException by applying the STAThread attribute to your Main() function. 
;~ Then you can use the Clipboard functions without any problems.

;~ .NET Framework Available since 3.0
;~ Namespace:   System.Windows
;~ Assembly:  PresentationCore (in PresentationCore.dll)
Func Example()
  Local $oAssCore = _CLR_LoadLibrary("mscorlib")
  Local $oAssSystem = _CLR_LoadLibrary("System")
  Local $oAssWindows = _CLR_LoadLibrary("System.Windows")
  Local $oAssPresCore = _CLR_LoadLibrary("PresentationCore")

  Local $oClipboard=_CLR_CreateObject($oAssPresCore, "System.Windows.Clipboard")

  ConsoleWrite("!$oClipboard " & IsObj($oClipboard) & @CRLF)

  $oClipboard.Clear()
  $oClipboard.SetText("Dot net opens some nice possibilities")
  local $strFromClipBoard=$oClipboard.GetText()

  consolewrite($strFromClipboard & @CRLF)

EndFunc

 

  • Melba23 pinned and unpinned this topic
Posted

Hi All,

Updated post 165 with the reworked examples based on the latest CLRv1... Also addes comments in the CLR.au3 to better explain what is what ...

Still a few to go Examples to add to the ZIP ... 

Please test and review and comment ...

 

Posted

Hello guys. Nice to see that all you don't give up in this nice project. I've been busy :S.

 

the clipboard can be used this way:

 

Example()

Func Example()
    Local $oAssembly = _CLR_LoadLibrary("System.Windows.Forms")
    ConsoleWrite("$oAssembly: " & IsObj($oAssembly) & @CRLF)

    Local $pAssemblyType = 0
    $oAssembly.GetType_2("System.Windows.Forms.Clipboard", $pAssemblyType)
    ConsoleWrite("$pAssemblyType = " & Ptr($pAssemblyType) & @CRLF)

    Local $oAssemblyType = ObjCreateInterface($pAssemblyType, $sIID_IType, $sTag_IType)
    ConsoleWrite("IsObj( $oAssemblyType ) = " & IsObj($oAssemblyType) & @CRLF)

    Local $aText[] = ["We Love AutoIt"]
    Local $sClipBoardText = ""

    $oAssemblyType.InvokeMember_3("Clear", 0x158, 0, 0, 0, 0)

    $oAssemblyType.InvokeMember_3("SetText", 0x158, 0, 0, CreateSafeArray($aText), 0)
    ConsoleWrite("Clipboard Data: " & ClipGet() & @CRLF)

    ClipPut("AutoIt Rocks!!!")
    $oAssemblyType.InvokeMember_3("GetText", 0x158, 0, 0, 0, $sClipBoardText)

    ConsoleWrite("Clipboard Data: " & $sClipBoardText & @CRLF)

EndFunc   ;==>Example

 

Saludos

Posted

HI DanyFirex, 

This is really a great contribution again, to learn form as well.

All credits of the .NET CLR UDF go to you and Larsj mainly !!  :graduated:

How did you figure out all the details, like taking the clipboard from 

System.Windows.Forms

and not from this NameSpace ?

"System.Windows"

Not even talking about why you took the "invokemember_3" and not for example "invokemember" ? 

Because it needed parameters, correct ?

if so where do we find the parameters ?

 

Posted

Hello

Really nothing special just I saw here

About using invokemember3 I saw here

 

Saludos

 

 

 

Posted (edited)

@Danyfirex,

The C++ Invokemember3 is a really nice example ! 

I am not an C++ or C# expert but I can follow the flow of the script. And it will answer al lot questions that Junkew had ...

So will update the Examples in the Zip with the working version you posted...

This working Example opens again a new opportunities .... :)

@Junkew

To Answer some of your questions like,  How to instantiate in AutoIt these hidden interfaces of mscorlib?

Quote

 odl,
      uuid(0F240708-629A-31AB-94A5-2BB476FE1783),
      hidden,
      dual,
      oleautomation,
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Random")

Some of the .Net Classes are exposed as traditional COM Objects... That's why you can use objCreate()

OR you can use  the CLSID and IID to instantiate the COM Object like this :

#AutoIt3Wrapper_UseX64=Y

Global $hActiveX

; Load the modules
If @AutoItX64 Then
    $hActiveX  = DllOpen("C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb")
Else
    $hActiveX  = DllOpen("C:\Windows\Microsoft.NET\Framework64\v2.0.50727\mscorlib.tlb")
EndIf
;MsgBox(0,"x64",@AutoItX64)


; Object identifiers
Global Const $sCLSID = "{4E77EC8F-51D8-386C-85FE-7DC931B7A8E7}"
;Global Const $sIID = "{3D54C6B8-D283-40E0-8FAB-C97F05947EE8}"
Global Const $sIID = Default ; Or use keyword Default if you want to use the Default interface ID

; COM Error monitoring
Global $oError = ObjEvent("AutoIt.Error", "_ErrFunc")

Func _ErrFunc()
    ConsoleWrite("! COM Error ! Number: 0x" & Hex($oError.number, 8) & " ScriptLine: " & $oError.scriptline & " - " & $oError.windescription & @CRLF)
    Return
EndFunc    ;==>_ErrFunc

; Instantiate the Object
Local $oDotNET = ObjCreate($sCLSID, $sIID, $hActiveX)

If $oDotNET = 0 Then MsgBox(16,"Error", "Could not create the object, Common problem ActiveX not registered.")

; This will create a System.Random Number

ConsoleWrite("System.Random # : " & $oDotNET.Next_2( 1, 100 ) & @CRLF)

$oDotNET = ""
DllClose($hActiveX)

In OLE/COM Viewer you will find all the information needed to find all the CLSID's and IID's, see .NET Category

Quote


CLSID =
{4E77EC8F-51D8-386C-85FE-7DC931B7A8E7} = System.Random
Implemented Categories
(62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}
InprocServer32 [Class] = System.Random
InprocServer32 [<no name>] = mscoree.dll
InprocServer32 [ThreadingModel] = Both
InprocServer32 [Assembly] = mscorl ib, Version = 1.0.5000.0, Culture = neutral, PublicKeyToken = b77a5c561934e089 
InprocServer32 [RuntimeVersion] = vl.1.4322
2.0.0.0 [Assembly] = mscorlib, Version=2.0.0.0, Culture =neutral, PublicKeyToken=b77a5c561934e089
2.0.0.0 [RuntimeVersion] = v2.0.50727
2.0.0.0 [Class] = System.Random
Progld = System.Random
System.Random = System.Random
CLSID = {4E77EC8F-51D8-386C-8SFE-7DC931B7A8E7}

So leaves us only the documentation, correct ?

Who want to take the liberty to do the release, I feel I am not entiltled to do so... !

 

Edited by ptrex
Posted (edited)

@ptrex 

  • I understand the classes for com that are in the registry but for that you do not need this thread then its just straightforward objcreate
  • Its easier to just read the MSCORLIB.IDL files in your flat text editor.
  • I am a bit confused on your objcreate syntax. Where is that in helpfiles/documentation? Also not in objCreateInterface documented in help.
     

Some additions

1. We need some examples that are not based on the registered com classes (see point 5)

2. We need to understand some things.
   I know you can go back to invoke_member stuff like @Danyfirex is doing but I do not understand why the createinstance / clr_createinstance is not working.

custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "InvokeMember")HRESULT _stdcall InvokeMember_3(
                        [in] BSTR name, 
                        [in] BindingFlags invokeAttr, 
                        [in] _Binder* Binder, 
                        [in] VARIANT Target, 
                        [in] SAFEARRAY(VARIANT) args, 
                        [out, retval] VARIANT* pRetVal);

3 the dot syntax working over the invoke_member syntax

4. Magic number 0x158 we should prevent 
    BindingFlags_Default | BindingFlags_Instance | BindingFlags_Static | BindingFlags_Public |  BindingFlags_FlattenHierarchy | BindingFlags_InvokeMethod

custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Reflection.BindingFlags")    
        BindingFlags_Default = 0,
        BindingFlags_IgnoreCase = 1,
        BindingFlags_DeclaredOnly = 2,
        BindingFlags_Instance = 4,
        BindingFlags_Static = 8,
        BindingFlags_Public = 16,
        BindingFlags_NonPublic = 32,
        BindingFlags_FlattenHierarchy = 64,
        BindingFlags_InvokeMethod = 256,
        BindingFlags_CreateInstance = 512,
        BindingFlags_GetField = 1024,
        BindingFlags_SetField = 2048,
        BindingFlags_GetProperty = 4096,
        BindingFlags_SetProperty = 8192,
        BindingFlags_PutDispProperty = 16384,
        BindingFlags_PutRefDispProperty = 32768,
        BindingFlags_ExactBinding = 0x00010000,
        BindingFlags_SuppressChangeType = 0x00020000,
        BindingFlags_OptionalParamBinding = 0x00040000,
        BindingFlags_IgnoreReturn = 0x01000000
    } BindingFlags;

5. an example to show the different ones. System.Environment is a good candidate that is not in the registry  but for me unclear why A and B fail

;~ System.Environment object A and B not working and C is working
exampleA() ;~ ==> COM Error intercepted !
exampleB() ;~ ==> No error at all (as its not by name in Registry)
exampleC() ;~ ==> Working: using invokemember

;~ .NET classes that do not need the whole thread on CLR.AU3 as they are directly in registry
exampleD() ;~ ==> Straight forward COM wrapped .NET made com object

Func ExampleA()
  Local $oAssCore = _CLR_LoadLibrary("mscorlib.dll")
  Local $oEnvironment = _CRL_CreateObject( $oAssCore, "System.Environment")
  ConsoleWrite("  ExampleA Interface _environment = <" & IsObj($oEnvironment)  & ">" & @Error & @CRLF)
EndFunc

Func ExampleB()
  Local $oEnvironment = objcreate("System.Environment")
  ConsoleWrite("  ExampleB Interface _environment = <" & IsObj($oEnvironment)  & ">" & @CRLF)
EndFunc

Func ExampleC()
    Local $oAssCore = _CLR_LoadLibrary("mscorlib.dll")
    ConsoleWrite("  $oAssCore: " & IsObj($oAssCore) & @CRLF)

    Local $pAssemblyType = 0
    $oAssCore.GetType_2("System.Environment", $pAssemblyType)
    ConsoleWrite("  ExampleC $pAssemblyType = " & Ptr($pAssemblyType) & @CRLF)

    Local $oAssemblyType = ObjCreateInterface($pAssemblyType, $sIID_IType, $sTag_IType)
    ConsoleWrite("  ExampleC IsObj( $oAssemblyType ) = " & IsObj($oAssemblyType) & @CRLF)

    Local $aEnvVar[] = ["ProgramFiles"]
    Local $sEnvValue=""

    $oAssemblyType.InvokeMember_3("GetEnvironmentVariable", 0x158, 0, 0, CreateSafeArray($aEnvVar), $sEnvValue)

    ConsoleWrite("  ExampleC Environment value for programfiles: " & $sEnvValue & @CRLF)

EndFunc   ;==>Example

Func ExampleD()
  Local $oRandom = objcreate("System.Random")
  ConsoleWrite("  ExampleD $oRandom = <" & IsObj($oRandom)  & ">" & @CRLF)
EndFunc

6. gettype_2 seems to behave like giving back a system.type (so that is good, we only miss then system.activator)

7. Probably logical but not instantiatable due to idl telling  noncreatable (you first need an assembly to use this reflection.assembly)

;~     [
;~       odl,
;~       uuid(17156360-2F1A-384A-BC52-FDE93C215C5B),
;~       version(1.0),
;~       dual,
;~       oleautomation,
;~       custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Reflection.Assembly")
;~     ]
;~     [
;~       uuid(28E89A9F-E67D-3028-AA1B-E5EBCDE6F3C8),
;~       version(1.0),
;~       noncreatable,
;~       custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Reflection.Assembly")
;~     ]
;~     coclass Assembly {
;~         interface _Object;
;~         [default] interface _Assembly;
;~         interface IEvidenceFactory;
;~         interface ICustomAttributeProvider;
;~         interface ISerializable;
;~     };

func exampleE()
    local $hActiveX

    ; Object identifiers
    Const $sCLSID = "{28E89A9F-E67D-3028-AA1B-E5EBCDE6F3C8}" ;~"System.Reflection.Assembly"
;~  Const $sCLSID = "{4E77EC8F-51D8-386C-85FE-7DC931B7A8E7}"

    Const $sIID = Default ; Or use keyword Default if you want to use the Default interface ID

    ; Load the modules
    If @AutoItX64 Then
        $hActiveX  = DllOpen("C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb")
    Else
        $hActiveX  = DllOpen("C:\Windows\Microsoft.NET\Framework64\v2.0.50727\mscorlib.tlb")
    EndIf
    ;MsgBox(0,"x64",@AutoItX64)

    ; Instantiate the Object
    Local $oDotNET = ObjCreate($sCLSID, $sIID, $hActiveX)

    If $oDotNET = 0 Then MsgBox(16,"Error", "Could not create the object, Common problem ActiveX not registered.")

; This will create a System.Random Number
ConsoleWrite("  System.Random # : " & $oDotNET.Next_2( 1, 100 ) & @CRLF)

    ConsoleWrite("  Now we should have output : " & $oDotNET.ToString() & @CRLF)

    $oDotNET = ""
    DllClose($hActiveX)

EndFunc

; COM Error monitoring
Global $oError = ObjEvent("AutoIt.Error", "_ErrFunc")

Func _ErrFunc()
    ConsoleWrite("! COM Error ! Number: 0x" & Hex($oError.number, 8) & " ScriptLine: " & $oError.scriptline & " - " & $oError.windescription & @CRLF)
    Return
EndFunc    ;==>_ErrFunc

 

Edited by junkew
Posted (edited)

@junkew  

I guess you are missing the point I wanted to make.

For sure you know conventional COM classes and where objCreate() is used for. 

But not everyone is as smart as you guys in this little workshop, and might get very confused, That in some cases .Net objects are available using conventional COM approach, And the majority is not? 

That's why I thew in this example that you can call conventional COM Libraries using the CLSID. 

Next thing is, and that is a bonus, is that you do not have to register the COM object. It is like RegFree approach,

Just to show that users can access (some) .Net object (RegFree) simular to .NET com Objects (ex. LoadLibrary) and access the Classes (ex. CreateInstance). Is this documented, not really - because it is also not supported probably .... ;)

Relating to .Net Controls I also found this : Microsoft InteropForms Toolkit 2.1

For the rest I don't have answers to all you questions unfortunately ... .NET is huge and there are probably still 1000 questions unanwered ?

But what's next .... do we release what we have or wait or ... 

 

 

Edited by ptrex
Posted

You can replace a lot of 10-line code blocks in CLR.au3 with CreateSafeArray function.

Replace

"ClearPrivatePath ) = 0; hresult();" & _
"ClearShadowCopyPath ) = 0; hresult();" & _

with

"ClearPrivatePath hresult();" & _
"ClearShadowCopyPath hresult();" & _

 

I think you (you and junkew) should release some code within too long. And then there will be better time to look at some of the remaining issues. Otherwise, no code will ever be released.

Posted

Hi Larsj, I made the modifications and tested all examples, 

Most of them work except the XPTABLE and the "System.Collections.ArrayList" one
 

Func Example()
  Local $oAppDomain = _CLR_GetDefaultDomain()

  ; Create a 1D array with one integer element
  Local $aArgs = [ 128 ], $psa =  $aArgs

  ; Create an instance of ArrayList using a parameterized constructor
  Local $pHandle
  $oAppDomain.CreateInstance_3( "mscorlib", "System.Collections.ArrayList", True, 0, 0, $psa, 0, 0, 0, $pHandle )
  ConsoleWrite( @CRLF & "$pHandle = " & $pHandle & @CRLF )

  Local $oHandle = ObjCreateInterface( $pHandle, $sIID_IObjectHandle, $sTag_IObjectHandle )
  ConsoleWrite( @CRLF & "IsObj( $oHandle ) = " & IsObj( $oHandle ) & @CRLF )

  ; Unwrap the ArrayList instance inside the ObjectHandle
  Local $oArrayList
  $oHandle.Unwrap( $oArrayList )
  ConsoleWrite( @CRLF & "IsObj( $oArrayList ) = " & IsObj( $oArrayList ) & @CRLF )

  ; Print ArrayList Capacity
  ConsoleWrite( @CRLF & "$oArrayList.Capacity() = " & $oArrayList.Capacity() & @CRLF )
EndFunc

So I will revert back to the one of post 165 for now ... 

It looked promising though :)

Posted

I'd just like to say a big thank-you to everyone who has contributed to this so far. I've not used it yet but I think this is going to be a very useful UDF library that will significantly increase the already huge number of tasks that AutoIt can be used to make our computing life easier.

Well done all.

"Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to build bigger and better idiots. So far, the universe is winning."- Rick Cook

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...