30. .NET Interop
MALDA provides native support for loading and using external .NET libraries, enabling access to the vast .NET ecosystem including NuGet packages, custom class libraries, and framework APIs. This capability allows MALDA programs to leverage existing .NET code without rewriting it.
30.1 Types already in the runtime
You do not need a custom DLL for core BCL types. Pass the fully qualified name to dotnetNew or getDotNetType. Generic types use backtick arity: List`1 means List<T> with one type parameter.
var sb = dotnetNew("System.Text.StringBuilder");
sb.Append("hello");
sb.Append(" ");
sb.Append("MALDA");
print(sb.ToString());
30.2 Loading .NET Assemblies
loadAssembly(pathOrName)
Loads a .NET assembly from a file path or by assembly name.
Syntax
var asm = loadAssembly("path/to/library.dll");
var asm = loadAssembly("System.Data"); // Load by assembly name
Parameters
pathOrName(string): Either a file path to a .dll file (absolute or relative) or an assembly name
Returns
object: An assembly object that can be used withgetDotNetType()
Behavior
- If the argument is a rooted path (e.g.,
C:\path\to\file.dll) or ends with.dll, it's treated as a file path - Otherwise, it's treated as an assembly name and loaded using
Assembly.Load() - Throws an exception if the assembly cannot be found or loaded
Example
// Load from file path
var customLib = loadAssembly("C:\\MyLibs\\MyLibrary.dll");
// Load by assembly name (from GAC or already loaded)
var systemData = loadAssembly("System.Data");
30.3 Getting .NET Types
getDotNetType(assembly, typeName) or getDotNetType(fullTypeName)
Retrieves a .NET type from an assembly or from all loaded assemblies.
Syntax
// From a specific assembly
var type = getDotNetType(asm, "MyNamespace.MyClass");
// From any loaded assembly (searches all loaded assemblies)
var type = getDotNetType("System.Collections.Generic.List`1");
Parameters
- Option 1:
assembly(object): Assembly object returned byloadAssembly() - Option 1:
typeName(string): Fully qualified type name (namespace + class name) - Option 2:
fullTypeName(string): Fully qualified type name (searches all loaded assemblies)
Returns
object: A type handle that can be used withdotnetNew()
Example
var asm = loadAssembly("MyLibrary.dll");
var myClassType = getDotNetType(asm, "MyLibrary.MyClass");
// Or search all loaded assemblies
var listType = getDotNetType("System.Collections.Generic.List`1");
List`1 for List<T>). The number after the backtick indicates the number of type parameters.
30.4 Creating .NET Objects
dotnetNew(typeOrTypeName, ...ctorArgs)
Creates a new instance of a .NET type.
Syntax
var obj = dotnetNew(typeHandle);
var obj = dotnetNew(typeHandle, arg1, arg2); // With constructor arguments
var obj = dotnetNew("System.Text.StringBuilder"); // Direct from type name
Parameters
typeOrTypeName(object or string): Either a type handle fromgetDotNetType()or a fully qualified type name string...ctorArgs(variadic): Constructor arguments (optional)
Returns
object: A .NET object instance that can be used to call methods and access properties
Example
// Using type handle
var type = getDotNetType(asm, "MyLibrary.MyClass");
var instance = dotnetNew(type);
// With constructor arguments
var instance2 = dotnetNew(type, "arg1", 42);
// Direct from type name
var sb = dotnetNew("System.Text.StringBuilder");
30.5 Using .NET Objects
Once a .NET object is created, you can:
- Call instance methods:
obj.MethodName(arg1, arg2) - Access properties:
obj.PropertyName(get) andobj.PropertyName = value(set) - Call static methods: Use
getDotNetType()to get the type, then call static methods on the type handle
Type Conversion
MALDA automatically converts between MALDA types and .NET types:
- MALDA integers →
int,long,short,byte,double,float - MALDA floats →
double,float,decimal - MALDA booleans →
bool - MALDA strings →
string - MALDA arrays → .NET arrays or
object[] - .NET objects → Wrapped as
DotNetObjectInstancefor method/property access
Example
// Load assembly and create instance
var asm = loadAssembly("C:\\path\\to\\TestLib.dll");
var fooType = getDotNetType(asm, "TestLib.Foo");
var foo = dotnetNew(fooType);
// Call instance method
var sum = foo.Add(2, 3);
print("2 + 3 = " + string(sum));
// Access and set properties
foo.Name = "from MALDA";
print("Foo.Name = " + foo.Name);
// Use a BCL type already loaded (no custom DLL)
var sb = dotnetNew("System.Text.StringBuilder");
sb.Append("item1");
sb.Append("item2");
print("Length: " + string(sb.Length));
Native callbacks
createNativeCallback(fn) wraps a MALDA function so it can be handed to .NET code that expects a delegate, for example an event handler or a callback parameter on an interop object. The argument must be a function value; anything else raises an error.
var callback = createNativeCallback((value) => {
print("called back with " + value);
});
nativeObject.onChanged(callback);
30.6 Using NuGet Packages
To use NuGet packages in MALDA:
- Create a .NET Class Library that references the NuGet package
- Build the library to generate a .dll file
- Load the assembly in MALDA using
loadAssembly() - Use the types from the package via
getDotNetType()anddotnetNew()
Example Workflow
// Load a library that uses a NuGet package (e.g., Newtonsoft.Json)
var jsonLib = loadAssembly("C:\\MyLibs\\JsonHelper.dll");
var jsonHelperType = getDotNetType(jsonLib, "JsonHelper.JsonProcessor");
var processor = dotnetNew(jsonHelperType);
// Use the library
var result = processor.ProcessJson(jsonString);
30.7 Complete Example
// Example: Using a custom .NET library
var asmPath = "C:\\MyLibraries\\MathUtils.dll";
var asm = loadAssembly(asmPath);
// Get a type from the library
var calculatorType = getDotNetType(asm, "MathUtils.Calculator");
var calc = dotnetNew(calculatorType);
// Use the calculator
var result = calc.Add(10, 20);
print("10 + 20 = " + string(result));
// Access properties
calc.Precision = 5;
print("Precision set to: " + string(calc.Precision));
30.8 Implementation Notes
- Assembly Loading: Uses
Assembly.LoadFrom()for file paths andAssembly.Load()for assembly names - Type Resolution: Searches loaded assemblies if type is not found in the specified assembly
- Method Overload Resolution: Automatically selects the best matching method overload based on parameter count and type compatibility
- Error Handling: Throws exceptions with descriptive messages if assemblies, types, or methods cannot be found
- Type Safety: Runtime type checking ensures method calls and property access are valid
30.9 Use Cases
- Leverage Existing .NET Libraries: Use well-established NuGet packages without rewriting code
- Custom Business Logic: Create .NET libraries for complex operations and call them from MALDA
- Performance-Critical Code: Write performance-sensitive code in C# and call it from MALDA
- Framework Integration: Access .NET Framework APIs (e.g.,
System.IO,System.Net,System.Data) - Third-Party Integrations: Use .NET SDKs for external services (databases, APIs, etc.)
See Also
- 12. Input/Output — console, files, paths, environment
- 11. Classes & Objects - Creating and using MALDA classes
- 33. Examples - Complete code examples