junkew Trusted Contributor.
Trusted Contributor.
755 views

C# compile on the fly thru dotnetfactory

Many people are not aware that since QTP 9.2 dotnetfactory is there. 

This is nice for the default classes but not many people now that you can compile your .NET source on the fly and as such offloading a lot of power to C# (or VB.NET if you want) for speed reasons instead of having VBScript doing it for you.

Below an example of the base stuff (maybe later I will post some more usefull examples) just returning a simple string from .NET class (no need for REGASM etc)

' Example from HP UFT to compile and call on the fly C# code (VB is also possobile offcourse)
' Speed issues in HP UFT can now easily be resolved with some C£ helpers
' Console.writeline is not writing to Output console of HP UFT, not a showstopper

call testcompilefrommemory

Sub testCompileFromMemory()
	Dim oCSharpCodeProvider
	Dim oCompilerParameters
	Dim strSourceCode 'C# string with simple class
	Dim oCompileResult
	
	Set oCSharpCodeProvider=getDOTNetInstance("Microsoft.CSharp.CSharpCodeProvider")
	set oCompilerParameters=getdotnetinstance("System.CodeDom.Compiler.CompilerParameters")
	
	oCompilerParameters.GenerateInMemory = true
    oCompilerParameters.GenerateExecutable = false
    
    ' Convert | delimited list of references into an array.
    ' References = "System.dll | System.Management.dll | System.Windows.Forms.dll"
    oCompilerParameters.ReferencedAssemblies.Add("system.dll")
    oCompilerParameters.ReferencedAssemblies.Add("System.Management.dll")
    oCompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll")   
    
    strSourceCode = "using System;" & vbcrlf
    strSourceCode = strSourceCode & "namespace hello " & vbcrlf
    strSourceCode = strSourceCode & "{" & vbcrlf
    strSourceCode = strSourceCode & "  public class HelloWorld {" & vbcrlf
    strSourceCode = strSourceCode & "    public HelloWorld() {" & vbcrlf
    strSourceCode = strSourceCode & "      Console.WriteLine(" & """" & "hello world coming from .NET" & """" & ");" & vbcrlf
    strSourceCode = strSourceCode & "    }" & vbcrlf
    strSourceCode = strSourceCode & "  }" & vbcrlf
    strSourceCode = strSourceCode & "  public class HelloWorld2 {" & vbcrlf
    strSourceCode = strSourceCode & "    public HelloWorld2() {" & vbcrlf
    strSourceCode = strSourceCode & "      Console.WriteLine(" & """" & "hello world 2 coming from .NET" & """" & ");" & vbcrlf
    strSourceCode = strSourceCode & "    }" & vbcrlf
    strSourceCode = strSourceCode & "    public string DoSomething() {" & vbcrlf
    strSourceCode = strSourceCode & "      return (" & """" & "hello world coming from .NET" & """" & ");" & vbcrlf
    strSourceCode = strSourceCode & "    }" & vbcrlf
    strSourceCode = strSourceCode & "  }" & vbcrlf	
    strSourceCode = strSourceCode & "}" & vbcrlf	
    
    print "*** Begin source ***"
	print strSourceCode
    print "*** End source ***"
	
	'Compile the source on the fly
	Set oCompileResult = oCSharpCodeProvider.CompileAssemblyFromSource(oCompilerParameters, strSourceCode)
	If isobject(oCompileResult) = true Then
		If oCompileResult.Errors.Count >= 1 Then
			print "compile errors are there, fix them" 
			print ocompileresult.errors.get_item(0).ErrorText
		End If
	
		'Make a reference to the assembly generated 	
		Set oAss=oCompileResult.CompiledAssembly
	
		'Be aware there is a difference between methods and properties regarding braces
		print "ToString: " & oAss.ToString()
		print "FullName: " & oAss.FullName
		print "GlobalAssemblyCache: " & oAss.GlobalAssemblyCache
		print "ScopeName: " & oAss.GetType().Module.ScopeName
		
		'Get all types, normally 1 class only but for demo just did 2 in the namespace (C# perfectly valid)
		'Get me an array of all types in this assembly
		Set oTypes=oAss.GetTypes()
	
		print "*** begin oTypes ***"
		print "oAllTypes " &  oTypes.ToString()
		print "Length: " & oTypes.Length
	
		'As we are mixing .NET and other things sometimes a .Count, .Length and sometimes an enumerator to handle
		set myEnum=oTypes.getenumerator()
		While myEnum.moveNext()
			set oType= myEnum.Current
			print oType.FullName & "-" & oType.AssemblyQualifiedName & "-" & oType.Name & "-" & oType.Namespace
		
			'This should print the members of the class
			set myEnum2=oType.GetMembers().getEnumerator		
			While myEnum2.moveNext()
				Set oMemberInfo=myEnum2.Current
				print vbtab & oMemberInfo.name
			Wend		
		Wend
	
		'In this example we just pick the 2nd class
		print "creating : " & oType.AssemblyQualifiedName	
		print "*** end oTypes ***"
		
		'As we use createinstance of the assembly no need for a fully qualified name (which is needed when using activator class)
		Set oHello=oAss.CreateInstance(oType.FullName)
	
		If isobject(oHello)=true Then
			print ""
			print "the object is there now how to call a method"
			print "ToString: " & oHello.ToString()
			print  "*** Fireworks begins ***" 
			print "DoSomething returning a value: " & oHello.DoSomething()		
			print  "*** Fireworks ends ***" 
			
		End If
		
	End If 	
End Sub

' Small wrapper to ease debugging info
Function getDOTNetInstance(tInstanceString)
	dim tObject
	set tObject = dotnetfactory.CreateInstance(tInstanceString)
	If isobject(tObject) = true Then
		print "hello object " & tInstanceString
	End If
	
	Set getDOTNetInstance=tObject
	
End Function
1 Reply
Highlighted
junkew Trusted Contributor.
Trusted Contributor.

Re: C# compile on the fly thru dotnetfactory

Another example based on this (and included callback with getref)

https://www.joecolantonio.com/2011/09/13/qtp-how-to-use-a-net-dll-in-quicktest-professionalfunctional-testing/

  • no need to register (regasm) your C# written class in registry.
    You just compile and run on the fly whatever you write in C# (or any .NET language)
  • no need for visual studio installation
  • you can offload some stuff to C# (if you need more speed / scanning of large files this could be an option)
  • C# calling back your VBScript function (so you can build your full framework in a .NET language and offload some click, settext object repository etc to your UFT functions)
  • If C# is a little out of your comfortzone you could try VB.NET which is maybe easier to understand when you come             from VBScript arena: you have to use this then  
          Microsoft.VisualBasic.VBCodeProvider instead of Microsoft.CSharp.CSharpCodeProvider

save this as uft_class.cls

using System;
using System.Collections.Generic;
using System.Text;

namespace UFT.UI.Automation
{
public class UFT
{
private object _UFTCallBackFunction = null;

public int Add(int firstNum, int secondNum)
{
return firstNum + secondNum;
}

public int callMeBack(int firstNum, int secondNum)
{
return firstNum + secondNum;
}

public int callMeBack2()
{
string[] retParts = {"Yep this is value 1"};
_UFTCallBackFunction.GetType().InvokeMember("", System.Reflection.BindingFlags.InvokeMethod, null, _UFTCallBackFunction, retParts);
return 0;
}

public void InitUFTCallBack(object UFTCallBackFunction)
{
_UFTCallBackFunction = UFTCallBackFunction;
}
}
}

and put this in a

  • new UFT test
' Example from HP UFT to compile and call on the fly C# code (VB is also possobile offcourse)
' Speed issues in HP UFT can now easily be resolved with some C£ helpers
' Console.writeline is not writing to Output console of HP UFT, not a showstopper but makes debugging harder

Const ForReading = 1
strCSharpFileName="UFT_CLASS.CLS"
Call testCompileFromFile(strCSharpFileName)

Sub testCompileFromFile(strCSharpFileName)
Dim oCSharpCodeProvider
Dim oCompilerParameters
Dim strSourceCode 'C# string with simple class
Dim oCompileResult

Set oCSharpCodeProvider=getDOTNetInstance("Microsoft.CSharp.CSharpCodeProvider")
set oCompilerParameters=getdotnetinstance("System.CodeDom.Compiler.CompilerParameters")

oCompilerParameters.GenerateInMemory = true
oCompilerParameters.GenerateExecutable = false

' Convert | delimited list of references into an array.
' References = "System.dll | System.Management.dll | System.Windows.Forms.dll"
oCompilerParameters.ReferencedAssemblies.Add("system.dll")
oCompilerParameters.ReferencedAssemblies.Add("System.Management.dll")
oCompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll")

'Read the file into a variable
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile (strCSharpFileName, ForReading)
strSourceCode = objTextFile.ReadAll
objTextFile.Close

print "*** Begin source ***"
print strSourceCode
print "*** End source ***"

'Compile the source on the fly
Set oCompileResult = oCSharpCodeProvider.CompileAssemblyFromSource(oCompilerParameters, strSourceCode)
If isobject(oCompileResult) = true Then
If oCompileResult.Errors.Count >= 1 Then
print "compile errors are there, fix them"
print ocompileresult.errors.get_item(0).Line & ocompileresult.errors.get_item(0).ErrorText

Exit sub
End If

'Make a reference to the assembly generated
Set oAss=oCompileResult.CompiledAssembly

'Be aware there is a difference between methods and properties regarding braces
print "ToString: " & oAss.ToString()
print "FullName: " & oAss.FullName
print "GlobalAssemblyCache: " & oAss.GlobalAssemblyCache
print "ScopeName: " & oAss.GetType().Module.ScopeName

'Get all types, normally 1 class only but for demo just did 2 in the namespace (C# perfectly valid)
'Get me an array of all types in this assembly
Set oTypes=oAss.GetTypes()

print "*** begin oTypes ***"
print "oAllTypes " & oTypes.ToString()
print "Length: " & oTypes.Length

'As we are mixing .NET and other things sometimes a .Count, .Length and sometimes an enumerator to handle
set myEnum=oTypes.getenumerator()
While myEnum.moveNext()
set oType= myEnum.Current
print oType.FullName & "-" & oType.AssemblyQualifiedName & "-" & oType.Name & "-" & oType.Namespace

'This should print the members of the class
set myEnum2=oType.GetMembers().getEnumerator
While myEnum2.moveNext()
Set oMemberInfo=myEnum2.Current
print vbtab & oMemberInfo.name
Wend
Wend

'In this example we just pick the first (and only) class
print "creating : " & oType.AssemblyQualifiedName
print "*** end oTypes ***"

'As we use createinstance of the assembly no need for a fully qualified name (which is needed when using activator class)
Set oTestCom=oAss.CreateInstance(oType.FullName)

If isobject(oTestCom)=true Then
print ""
print "the object is there now how to call a method"
print "ToString: " & oTestCom.ToString()
print "*** Fireworks begins ***"
print "DoSomething returning a value: " & oTestCom.Add(6,4)

Set oCallMe = GetRef("CallMeBackWithAParameter")
oTestCom.InitUFTCallBack(oCallMe)
oTestCom.callMeBack2()

print "*** Fireworks ends ***"

End If

End If
End Sub

' Small wrapper to ease debugging info
Function getDOTNetInstance(tInstanceString)
dim tObject
set tObject = dotnetfactory.CreateInstance(tInstanceString)
If isobject(tObject) = true Then
print "hello object " & tInstanceString
End If

Set getDOTNetInstance=tObject

End Function

Function callMeBackWithAParameter(P1)
print "I wass called back from C# having value " & P1
End Function

  

0 Likes
The opinions expressed above are the personal opinions of the authors, not of Micro Focus. By using this site, you accept the Terms of Use and Rules of Participation. Certain versions of content ("Material") accessible here may contain branding from Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company. As of September 1, 2017, the Material is now offered by Micro Focus, a separately owned and operated company. Any reference to the HP and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE marks are the property of their respective owners.