Tip: How to use Win32 API or DLL Calls with Roslyn Compiler for the Windows 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.

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