Tip: How to use Win32 API or DLL Calls with Roslyn Compiler for the Windows and Cross-platform Compatibility Mode

Almost a year ago I introduced the possibility to make Win32 API or DLL calls. However, this approach no longer works with .NET5+, which is Windows compatibility mode in UiPath. So I investigated if there is a possibility to develop this approach for .NET5+ as well. One possible approach can be implemented using the Roslyn compiler. It is part of .NET SDK and also part of the UiPath installation. To be absolute sure install the package Microsoft.CodeAnalysis via Manage Packages.

This Invoke Code Activity has the same interface as in the other example. Thus, the routines can be exchanged at will. The procedure is very similar to the other example, except that the Roslyn compiler is used here.

//-UiPath Begin---------------------------------------------------------

//-Checking the code----------------------------------------------------
if(System.String.IsNullOrEmpty(Code.Trim())) {
  ErrorMessage = "Code missed";
  return;
}

//-Metadata for Compilation---------------------------------------------
Microsoft.CodeAnalysis.MetadataReference[] references = new Microsoft.CodeAnalysis.MetadataReference[] {
  Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
  Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};

//-Path to UiPath Studio------------------------------------------------
string pathDotNet = @System.IO.Path.GetDirectoryName(
  System.Reflection.Assembly.GetExecutingAssembly().CodeBase
).Remove(0, 6)+ @"\";

//-Add assemblies to references-----------------------------------------
string[] assemblies = Assemblies.Split(new char[] { ',' } );
try {
  foreach(string Assembly in assemblies) {
    Array.Resize<Microsoft.CodeAnalysis.MetadataReference>(ref references, references.Length + 1);
    references[references.Length - 1] = Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(pathDotNet + Assembly);
  }
} catch(Exception ex) {
  ErrorMessage = ex.Message + Environment.NewLine + ex.StackTrace;
  return;
}

//-Compiling of the code------------------------------------------------
string assemblyName = Path.GetRandomFileName();
Microsoft.CodeAnalysis.SyntaxTree syntaxTree;
MemoryStream memStream = new MemoryStream();
Microsoft.CodeAnalysis.Emit.EmitResult compileResult;
switch(Language.ToUpper()) {
  case "CS" :
    syntaxTree = Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(Code);
    Microsoft.CodeAnalysis.CSharp.CSharpCompilation csharpCompilation = Microsoft.CodeAnalysis.CSharp.CSharpCompilation.Create(
      assemblyName,
      syntaxTrees: new[] { syntaxTree },
      references: references,
      options: new Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
    );
    compileResult = csharpCompilation.Emit(memStream);
    break;
  case "VB" :
    syntaxTree = Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree.ParseText(Code);
    Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation vbCompilation = Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation.Create(
      assemblyName,
      syntaxTrees: new[] { syntaxTree },
      references: references,
      options: new Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
    );
    compileResult = vbCompilation.Emit(memStream);
    break;
  default:
    ErrorMessage = "Language unknown";
    return;
}

if(compileResult.Success) {
  //-Compilation was successfully and produces a Library----------------

  //-Read method parameters and add it to a list------------------------
  object[] MethodParams = null;
  if(!System.String.IsNullOrEmpty(MethodParameters)) {
    string[] partsMethodParams = MethodParameters.Split(new char[] { ',' });
    MethodParams = new List<string>(partsMethodParams).ToArray();
  }

  //-Read constructor parameters and add it to a list-------------------
  object[] ConstructorParams = null;
  if(!System.String.IsNullOrEmpty(ConstructorParameters)) {
    string[] partsConstructorParams = ConstructorParameters.Split(new char[] { ',' });
    ConstructorParams = new List<string>(partsConstructorParams).ToArray();
  }

  try {

    memStream.Seek(0, SeekOrigin.Begin);
    System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(memStream.ToArray());

    Type type = assembly.GetType(Instance);
    object oInstance = Activator.CreateInstance(
      type,
      System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.CreateInstance,
      null,
      ConstructorParams,
      null,
      null
    );

    if(!System.String.IsNullOrEmpty(Method)) {

      object oResult = type.InvokeMember(
        Method,
        System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.InvokeMethod,
        null,
        oInstance,
        MethodParams
      );

      if(oResult != null) {
        Result = oResult.ToString();
      }

    }

  } catch(Exception ex) {
    ErrorMessage = ex.Message + Environment.NewLine + ex.StackTrace;
  }

} else {
  //-Compilation was not successfully-----------------------------------

  IEnumerable<Diagnostic> failures = compileResult.Diagnostics.Where(diagnostic => 
    diagnostic.IsWarningAsError || 
    diagnostic.Severity == DiagnosticSeverity.Error);

  foreach(Diagnostic diagnostic in failures) {
    ErrorMessage += diagnostic.Id + ": " + diagnostic.GetMessage();
  }

}

//-UiPath End-----------------------------------------------------------

Here a tiny example to call the Win32 API function.

Here the invoked code. In this example I tried the same code as in the other example, but here in Visual Basic language:

Imports System
Imports System.Runtime.InteropServices

Class Test

  <DllImport(""user32.dll"")> _
  Public Shared Function MessageBox(HWnd As Integer, _
    text As String, caption As String, type As Integer) As Integer
  End Function

  Function Main() As String
    Dim Result As Integer
    Result = MessageBox(0, ""Hello World"", _
      ""MessageBox From Win32 API"", 0)
    Return Result.ToString()
  End Function

End Class

And it works as expected.

image

Here my example workflow:
DLLCall.xaml (16.4 KB)

Conclusion

Although the compatibility between Windows Legacy (dotNET Framework 4.6.1 - x86) and Windows (.NET5+ - x64) mode is very high, differences may exist at the edge. As we see here, this can be the case just in Invoke Code activities. The flexibility we achieve with this and the possibilities it offers us are very nice for us, but, like in this case, it can also brings us adaptation efforts. Nevertheless, we can continue to use the ability to call Win32 API and DLL functions in this environment with this approach.

Enjoy it.

3 Likes

I tried the same workflow with another example to call a native x64 DLL with C# language in Windows compatibility mode (.NET6 - x64) in VB language style:

"//-Begin----------------------------------------------------------------

using System;
using System.Runtime.InteropServices;
using System.Text;

class Pic2Xml {

  [DllImport(""pic2xml_x64.dll"", CharSet = CharSet.Unicode, EntryPoint = ""pic2xml"")]
  public static extern int pic2xml(StringBuilder PicName, int ExcelOptimization = 0);

  public static int Main(string PicName) {

    StringBuilder picName = new StringBuilder(PicName);
    return pic2xml(picName, 1);

  }

}

//-End------------------------------------------------------------------"

image

It works without any problems with UiPath version 22.7.

I tried the approach above in a Linux environment to see whether it is really cross-platform ready.

Here the source code, with slightly modified path, to the dotNET SDK:

Here my test code, a simple output to the console of a string via the method arguments, in this case “Hello World”:

using System;

namespace CompileSampleRoslyn {
  public class Writer {
    public void Write(string message) {
      Console.WriteLine(message);
    }
  }
}

Here the PowerShell console:

Basically it can be stated that this approach is cross-platform capable.