How to use .NET from LoadRunner

Although LoadRunner has a good API for various text processing, sometimes it is still not enough, and then you have to expand it with generic functions. Often such implementations become the invention of the bicycle, since almost all tasks, as you know, have already been solved by someone. In addition, since I have a good background in C #, when solving a problem, one often thinks that this task would be easily solved if I had the .NET Framework class library at hand. In principle, if I were a Java programmer, I would have had similar thoughts about Java (which also has almost everything), but since .NET is closer to me, then we will talk about it. As a side effect, this article will be useful for those who want to learn how to call CLR code from native code.

.NET and Java in LoadRunner


First, consider an attractive but bad option. In principle, both .NET and Java work directly in LoadRunner. For each of these platforms, there are classes that are wrappers over the standard LoadRunner API. You can use them right away by selecting the .NET and Java Vuser modes, respectively . Let's say right away: these script development modes are created for a slightly different one. The .NET mode allows you to record the activity of a .NET application and create a script that calls directly the application's class methods. Java Vuser has a full-fledged and documented API for Java, but it doesn’t have a recording mode at all (it has Java Record Replay in the same way as .NET) For this reason, using them for the Web is very problematic, and in general, “using” in this case means “writing somehow working code” and nothing more. When developing load scripts from a tool, it is important to be able to record traffic and convert it into a script, even a draft one, which will be further developed. But here's the problem: recording web traffic ( Web mode - HTTP / HTML) is only possible with conversion to C code. The community has been waiting for this for a long time, and I honestly hoped that at least in the new version 12.00 it would be possible to choose C # or Java, but this did not happen. In addition, the API for .NET is practically not documented, and you have to act by stepping on a rake in the dark (by the way, the method signatures for .NET and Java wrappers are also different). If you can find the required wrapper method in .NET classes relatively quickly, I don’t know how to pass parameters to it.

For example:

namespace Script
{
    public partial class VuserClass
    {
        public int Action()
        {
        	web.url("ya.ru", "URL=http://ya.ru/", null, null);
            return 0;
        }
    }
}


This code works and makes a request equivalent to the C function web_url () . The method is attractive in that you can connect any .NET libraries in Run-Time Settings and use them immediately, however, questions immediately arise:

  • How to pass web.url () parameters like web_url () ?
  • How to make it all work through a proxy?
  • Why does the good part of Run-Time Settings disappear in this mode? (In principle, it’s understandable why, but this is not easier.)


In general, this mode is not made for that, therefore, despite the potential convenience of this option, we will not use it for other purposes and will remain in C. Nevertheless, the ability to quickly overwrite a piece of script is more significant. At the same time, with a few simple steps, you can call the .NET code from the LoadRunner C script.

LoadRunner regularly supports calling native libraries, for this there is a function lr_load_dll () . In order to call .NET, you have to write a native layer and, in fact, the whole question comes down to calling the CLR code from native code. Who knows how to do this, you can skip the next section, for the rest I will tell you how to do this.

Native DLL with access to .NET


Once I needed to decode a string of the form:

Создать заявку

This is a kind of encoding used inside XML and HTML: each character is represented as UTF-8 code. I did not succeed in doing this on LoadRunner (if anyone knows how, please poke my nose). But in the Dottnet class HttpUtility there is an HtmlDecode () method that does it perfectly. Let's see how you can zayuzat it.

Requirements

Naturally, you will need installed in the system. NET Framework. Specifically, this method is in any version starting from 2.0 (or maybe earlier, but it doesn’t matter anymore), but remember that the native library indicates which version we are accessing, and different versions are not interchangeable. Also, remember that Win7 / 2008 already has .NET FW 3.5 installed, so if you use load stations on these OSs, you don’t need to install anything, you just need to indicate inside the sipus plus library that we are using the .NET Framework 3.5.

Writing a DLL

For some time now in Visual Studio it has become very easy to access .NET classes from C ++ code. To do this, you need to set the Common Language Runtime Support mode in the project settings . Next, go to the project properties, Common Properties, Framework and References and add references to the assemblies that you want to use. In this case, we are interested in System.Web from Assemblies / Framework. After that, you can already access the .NET classes, however, in the C ++ syntax:

#include "..\LR include 11.50\lrun.h"
//...
int xml_http_decode(const char* inputStr, const char* outputParam)
{
	try
	{
		System::String^ temp = gcnew System::String(inputStr);
		System::String^ result = HttpUtility::HtmlDecode(temp);
		marshal_context^ context = gcnew marshal_context();
		lr_save_string(context->marshal_as(result), outputParam);
	}
	catch(char* message)
	{
		lr_save_string(message, outputParam);
		return LR_FAIL;
	}
	catch(...)
	{
		lr_save_string("!!! Unknown exception raised !!!", outputParam);
		return LR_FAIL;
	}
	return LR_PASS;
}

LoadRunner can only connect native dlls, so we declare a function with a C-compatible signature and return value. Next, the "^" sign is used to declare .NET types and distinguish them from C ++ types. We must create CLR strings from C lines to pass them to .NET methods. The gcnew operator is used to create CLR objects .

So that the function can be called from outside the dll, it must be exported. For this we write:

extern "C"
{
	__declspec( dllexport ) int xml_http_decode(const char* inputStr, const char* outputParam);
}

Next, we connect the library to LoadRunner and calmly call our function:

lr_load_dll("hplr.dll");	// Native DLL на C++.
xml_http_decode(
    "Создать "
    "заявку",
    "p_decoded");
lr_output_message("%s", lr_eval_string("{p_decoded}")); // В параметре содержится раскодированное значение.

If in LoadRunner the lr_load_dll () function crashes with the confusing error “I can’t find the file,” then it may not be the plug-in DLL itself, but its dependencies. To successfully connect a library built in Debug mode, you need to add the msvcp110d.dll and msvcr110d.dll files in System32 or in SysWOW64 for 32-bit and 64-bit OS, respectively. Other dependencies can be investigated using Dependency Walker or the like. If the library is compiled in Release mode, then nothing additional is needed (remove also the additional #include and dependencies in the compiler settings).

Functions written in C ++ are available in the DLL immediately (do not forget to export!). You can write in LoadRunner
relative path to the DLL, starting with the script folder. There is no need to restart VuGen or reopen the script.

This way we can access ready-made .NET classes. But writing my C ++ code for working with .NET, in my opinion, is somewhat inconvenient, for this there are more suitable languages.
Note: in C ++ there are also a bunch of ready-made libraries (and they will work faster, by the way), but firstly, they need well-known experience and accuracy, and secondly, this is beyond the scope of this article.

Calling a custom library in C #


Suppose we wrote more complex logic in C # and packaged it in a separate assembly. How to access her from LR?
In principle, everything is the same as in the previous case, only the link needs to be added to our assembly (via Solution or Browse) and the classes need to be called from our assembly.

C # DLL

For some reason, LoadRunner can search for regular expressions only in server responses, and how to find a regular expression in the C-string or in the parameter value is not clear (if anyone knows, poke my nose). To solve this problem, you can write this function:

// C#-метод, ищущий в строке input регулярное выражение pattern и возвращающий
// группу номер nGroup вхождения с номером nMatch.
namespace HplrCs
{
    public static class HplrHelper
    {
        public static string GetRegexMatch(string input, string pattern, int nMatch, int nGroup)
        {
            try
            {
                var re = new Regex(pattern);
                var matches = re.Matches(input);
                if (matches.Count < nMatch + 1)
                    return String.Empty;
                var match = matches[nMatch];
                if (match.Success)
                {
                    if (match.Groups.Count < nGroup + 1)
                        return String.Empty;
                    return match.Groups[nGroup].Value;
                }
                else
                    return String.Empty;
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
        }
    }
}

We must put the assembly with this code in the Global Assembly Cache (GAC). To do this, you need to use the gacutil.exe utility, which is part of the Windows SDK, and is also installed with Visual Studio. For the .NET Framework 4.0 / 4.5, you need to use the corresponding version of gacutil.exe from the 8th version of the SDK, earlier versions will not be able to install the 4.0 / 4.5 builds.

gacutil.exe -i HplrCs.dll

Installation in the GAC must be done under admin rights. You can verify that the assembly is present in the cache as follows:

gacutil.exe -l HplrCs

To replace the version of the assembly in the GAC, you need to perform the installation again over the previous one. You do not need to delete the old version, but it is important to make sure that the installation was successful.

Writing a native wrapper

Again, similarly to the first option, we will create a native library that will be the link between LoadRunner and the .NET assembly:

#include "..\LR include 11.50\lrun.h"
//...
extern "C"
{
	__declspec( dllexport ) int get_regex_match(
		const char* inputStr, const char* pattern,
		const char* outputParam, int nMatch, int nGroup
	);
}
int get_regex_match(const char* inputStr, const char* pattern, const char* outputParam, int nMatch, int nGroup)
{
	try
	{
		System::String^ _inputStr = gcnew System::String(inputStr);
		System::String^ _pattern = gcnew System::String(pattern);
		System::String^ result = HplrHelper::GetRegexMatch(_inputStr, _pattern, nMatch, nGroup);
		marshal_context^ context = gcnew marshal_context();
		lr_save_string(context->marshal_as(result), outputParam);
	}
	catch(char* message)
	{
		lr_save_string(message, outputParam);
		return LR_FAIL;
	}
	catch(...)
	{
		lr_save_string("!!! Unknown exception raised !!!", outputParam);
		return LR_FAIL;
	}
	return LR_PASS;
}


Functions and Constants LR


You've probably noticed that the above code uses LoadRunner functions and constants. This is possible thanks to what we have done.

#include "..\LR include 11.50\lrun.h"


For this to correctly assemble, you need to add the lrun50.lib library to the linker input. They lie, respectively, in

C:\Program Files (x86)\HP\LoadRunner\include
C:\Program Files (x86)\HP\LoadRunner\setup\dot_net\Vc9\VCWizards\LrCVuserDllLibrary\templates\1033


With their help, you can call the LoadRunner API functions in the same way as if you had called them from a script.

Example


There is a completely ready-made template in the form of a MS Visual Studio 2012 project. You can take it from the link .

Archive Content:

  • C ++ is a project that demonstrates code calls both directly in the .NET Framework and in other .NET assemblies.
  • C Sharp is an example of a library (assembly) in .NET.
  • LR include * and LR lib * are the files from the LoadRunner distribution, copied to enable the project to be built if LoadRunner is not installed.
  • Output - collected binaries.
  • LR Ext lib usage example is an example of LoadRunner script using the above approach.

When you first open the project in Visual Studio, you will be prompted to update the version of the .NET Framework used. This should not be done if you do not understand the meaning of what is happening. Updating the version will oblige you to build the .NET assembly with this version as well as the Target Framework (in the project properties). By the way, I did not find where in the interface you can change the version of the .NET Framework for a C ++ project. It is shown in the properties of a C ++ project, but only for viewing, you cannot change it. But this can be done by opening the .vcxproj file in a text editor and finding the XML element TargetFrameworkVersion. Therefore, if you still updated the project, edit TargetFrameworkVersion to the one you need, or switch to using another version of the .NET Framework.

Performance


The performance of the proposed approach will be all the more attractive, the less you intend to use the built-in functions and the more you want to use the code in LR in C. If for any purpose there is a built-in LoadRunner function, you should use it, because it has native code behind it, which means that it will work faster.

For example, let's try to find a substring in a string with a length of approximately 32KB (it is difficult to make a longer one, because this is the maximum that allows LoadRunner to allocate on the stack for local parameters). I specifically take substring search as one of the classical problems, because the algorithms for it, presumably, should be optimized as much as possible.

	#define BUFF_SIZE 32700
	char buff[BUFF_SIZE];
	int i;
	lr_load_dll("hplr.dll");	// Native DLL.
	memset(buff, '-', BUFF_SIZE);
	buff[BUFF_SIZE - 1] = 0;
	strcpy(buff + BUFF_SIZE - 4, "+++");
	lr_start_transaction("Find substring, internal function");
	for (i = 0; i < 100000; i++)
		strstr(buff, "+++");
	lr_end_transaction("Find substring, internal function", LR_AUTO);
	lr_start_transaction("Find substrings, C# function");
	for (i = 0; i < 100000; i++)
		find_substr_net(buff, "+++");
	lr_end_transaction("Find substrings, C# function", LR_AUTO);


I will not cite .NET code, it is followed by the usual String.IndexOf () . Measurements should be carried out only in the start mode in Controller; in VuGen, execution is two orders of magnitude slower.

Find substring, internal function:  1,505 
Find substring, C# function:  13,323


Well, as expected. The main execution time is actually the search for a substring, and here the native code gives a significant advantage. The conversion of const char * to System.String also slows down the process .
But we have something to answer. Make the interpreter work independently. To do this, we write a function that performs simple actions in integer arithmetic:

int int_arithm_lr(int p)
{
	int i;
	int s = p;
	for (i = 0; i < 10000; i++)
	{
		s += i * (i % 2 * 2 - 1);
	}
	return s;
}


C # code is exactly the same, only with the words public static.
Compare the execution speed in both runtimes:

	lr_start_transaction("Integer arithmetics, LR function");
	for (i = 0; i < 500; i++)
		int_arithm_lr(i);
	lr_end_transaction("Integer arithmetics, LR function", LR_AUTO);
	lr_start_transaction("Integer arithmetics, C# function");
	for (i = 0; i < 500; i++)
		int_arithm_net(i);
	lr_end_transaction("Integer arithmetics, C# function", LR_AUTO);

Integer arithmetics, LR function:  45,772
Integer arithmetics, C# function:   0,013


The difference is 3.5 thousand times.

Although the use of such calculations is not typical for load testing scenarios, it exposes the weakness of the LR interpreter: the low speed of the native code. Therefore, if you need to write something sophisticated, then in .NET it will not only be more convenient to implement it, but it will also work much faster than the C code in LR.

That's all, perhaps, thanks for your attention!

Also popular now: