Using Pascal Libraries with .NET and Mono: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: Using Pascal Libraries with .NET and Mono
| Old | New | Line | |
|---|---|---|---|
| 1 | + | ## Introduction | |
| 2 | + | ||
| 3 | + | With the .NET framework, Microsoft provided excellent compatibility for "unmanaged" code. While unmanaged code sounds like something wild and dangerous, it just means so-called "legacy" code in the form of native Windows libraries. The [Mono](http://www.mono-project.com/Main_Page) project included similar support for native libraries on Linux and macOS. What this means is that you can compile libraries with Free Pascal and use them with .NET apps on Windows and with Mono apps on Linux and macOS. | |
| 4 | + | ||
| 5 | + | ## A simple Pascal library | |
| 6 | + | ||
| 7 | + | Copy and save this Pascal code to file SimpleLib.pas: | |
| 8 | + | ||
| 9 | + | ``` pascal | |
| 10 | + | library SimpleLib; | |
| 11 | + | ||
| 12 | + | function MySucc(AVal : Int64) : Int64; stdcall; | |
| 13 | + | begin | |
| 14 | + | Result := System.Succ(AVal); | |
| 15 | + | end; | |
| 16 | + | ||
| 17 | + | function MyPred(AVal : Int64) : Int64; stdcall; | |
| 18 | + | begin | |
| 19 | + | Result := System.Pred(AVal); | |
| 20 | + | end; | |
| 21 | + | ||
| 22 | + | exports | |
| 23 | + | MySucc, | |
| 24 | + | MyPred; | |
| 25 | + | ||
| 26 | + | end. | |
| 27 | + | ``` | |
| 28 | + | ||
| 29 | + | Now compile the library with Free Pascal: | |
| 30 | + | ||
| 31 | + | ``` bash | |
| 32 | + | fpc -Sd SimpleLib.pas | |
| 33 | + | ``` | |
| 34 | + | ||
| 35 | + | On Windows, this will create file simplelib.dll. On macOS, this will create file libsimplelib.dylib. On Linux, this will create file simplelib.so. On macOS and Linux, rename the compiled library file to simplelib.dll: | |
| 36 | + | ||
| 37 | + | ``` bash | |
| 38 | + | mv libsimplelib.dylib simplelib.dll | |
| 39 | + | ``` | |
| 40 | + | ||
| 41 | + | ## A simple VB.NET app | |
| 42 | + | ||
| 43 | + | Copy and save this VB.NET code to file TestLib.vb: | |
| 44 | + | ||
| 45 | + | ``` vb | |
| 46 | + | Imports System | |
| 47 | + | Imports System.Runtime.InteropServices | |
| 48 | + | ||
| 49 | + | Public Class TestLib | |
| 50 | + | ||
| 51 | + | Const SimpLibName = "simplelib.dll" | |
| 52 | + | ||
| 53 | + | 'Declare external functions using the DllImport attribute. | |
| 54 | + | <DllImport(SimpLibName, EntryPoint:="MySucc", _ | |
| 55 | + | CallingConvention:=CallingConvention.StdCall)> _ | |
| 56 | + | Public Shared Function Succ(ByVal AVal As Long) As Long | |
| 57 | + | End Function | |
| 58 | + | ||
| 59 | + | <DllImport(SimpLibName, EntryPoint:="MyPred", _ | |
| 60 | + | CallingConvention:=CallingConvention.StdCall)> _ | |
| 61 | + | Public Shared Function Pred(ByVal AVal As Long) As Long | |
| 62 | + | End Function | |
| 63 | + | ||
| 64 | + | Public Shared Sub Main() | |
| 65 | + | ||
| 66 | + | Dim TestVal As Long | |
| 67 | + | ||
| 68 | + | Try | |
| 69 | + | TestVal = 123 | |
| 70 | + | Console.WriteLine("Value is " & TestVal) | |
| 71 | + | Console.WriteLine("Successor is " & Succ(TestVal)) | |
| 72 | + | Console.WriteLine("Predecessor is " & Pred(TestVal)) | |
| 73 | + | Catch e As Exception | |
| 74 | + | Console.WriteLine(e) | |
| 75 | + | End Try | |
| 76 | + | ||
| 77 | + | End Sub 'Main | |
| 78 | + | ||
| 79 | + | End Class 'TestLib | |
| 80 | + | ``` | |
| 81 | + | ||
| 82 | + | If you have the .NET framework installed on Windows, you can compile this code as follows: | |
| 83 | + | ||
| 84 | + | [pathto]vbc TestLib.vb | |
| 85 | + | ||
| 86 | + | where \[pathto\] is the path to the .NET framework (example: c:\windows\microsoft.net\framework\v1.1.4322\\ with .NET 1.1). This will create file TestLib.exe. | |
| 87 | + | ||
| 88 | + | If you have Mono installed, you can also try compiling this code as follows: | |
| 89 | + | ||
| 90 | + | vbnc TestLib.vb | |
| 91 | + | ||
| 92 | + | You can also try compiling the C# version: | |
| 93 | + | ||
| 94 | + | ## A simple C# app | |
| 95 | + | ||
| 96 | + | Here's the equivalent C# code. Copy and save it to file TestLib.cs. | |
| 97 | + | ||
| 98 | + | ``` csharp | |
| 99 | + | using System; | |
| 100 | + | using System.Runtime.InteropServices; | |
| 101 | + | ||
| 102 | + | public class TestLib { | |
| 103 | + | ||
| 104 | + | const string SimpLibName = "simplelib.dll"; | |
| 105 | + | ||
| 106 | + | //Declare external functions using the DllImport attribute. | |
| 107 | + | [DllImport(SimpLibName, EntryPoint="MySucc", | |
| 108 | + | CallingConvention=CallingConvention.StdCall)] | |
| 109 | + | public static extern long Succ(long AVal); | |
| 110 | + | ||
| 111 | + | [DllImport(SimpLibName, EntryPoint="MyPred", | |
| 112 | + | CallingConvention=CallingConvention.StdCall)] | |
| 113 | + | public static extern long Pred(long AVal); | |
| 114 | + | ||
| 115 | + | public static void Main() | |
| 116 | + | { | |
| 117 | + | long TestVal; | |
| 118 | + | ||
| 119 | + | try | |
| 120 | + | { | |
| 121 | + | TestVal = 123; | |
| 122 | + | Console.WriteLine("Value is " + TestVal); | |
| 123 | + | Console.WriteLine("Successor is " + Succ(TestVal)); | |
| 124 | + | Console.WriteLine("Predecessor is " + Pred(TestVal)); | |
| 125 | + | } | |
| 126 | + | catch(Exception e) | |
| 127 | + | { | |
| 128 | + | Console.WriteLine(e); | |
| 129 | + | } | |
| 130 | + | } | |
| 131 | + | } | |
| 132 | + | ``` | |
| 133 | + | ||
| 134 | + | With .NET, you can compile it as follows: | |
| 135 | + | ||
| 136 | + | [pathto]csc TestLib.cs | |
| 137 | + | ||
| 138 | + | With Mono, you can compile it as follows: | |
| 139 | + | ||
| 140 | + | mcs TestLib.cs | |
| 141 | + | ||
| 142 | + | With both .NET and Mono, compiling will create file TestLib.exe. | |
| 143 | + | ||
| 144 | + | ## A simple Pascal app using Oxygene | |
| 145 | + | ||
| 146 | + | Copy and save this Pascal code to file TestLib.pas: | |
| 147 | + | ||
| 148 | + | ``` pascal | |
| 149 | + | namespace TestLib; | |
| 150 | + | ||
| 151 | + | interface | |
| 152 | + | ||
| 153 | + | uses | |
| 154 | + | System.Runtime.InteropServices; | |
| 155 | + | ||
| 156 | + | const | |
| 157 | + | SimpleLibName = 'simplelib.dll'; | |
| 158 | + | ||
| 159 | + | type | |
| 160 | + | TestLib = class | |
| 161 | + | private | |
| 162 | + | [DllImportAttribute(SimpleLibName, EntryPoint:='MySucc', | |
| 163 | + | CallingConvention:=CallingConvention.StdCall)] | |
| 164 | + | class method Succ(AVal : Int64) : Int64; external; | |
| 165 | + | ||
| 166 | + | [DllImportAttribute(SimpleLibName, EntryPoint:='MyPred', | |
| 167 | + | CallingConvention:=CallingConvention.StdCall)] | |
| 168 | + | class method Pred(AVal : Int64) : Int64; external; | |
| 169 | + | public | |
| 170 | + | class method Main; | |
| 171 | + | end; | |
| 172 | + | ||
| 173 | + | implementation | |
| 174 | + | ||
| 175 | + | class method TestLib.Main; | |
| 176 | + | var | |
| 177 | + | TestVal : Int64; | |
| 178 | + | begin | |
| 179 | + | try | |
| 180 | + | TestVal := 123; | |
| 181 | + | Console.WriteLine('Value is ' + TestVal); | |
| 182 | + | Console.WriteLine('Successor is ' + Succ(TestVal)); | |
| 183 | + | Console.WriteLine('Predecessor is ' + Pred(TestVal)); | |
| 184 | + | except | |
| 185 | + | on E: Exception do | |
| 186 | + | Console.WriteLine(E.Message); | |
| 187 | + | end; | |
| 188 | + | end; | |
| 189 | + | ||
| 190 | + | end. | |
| 191 | + | ``` | |
| 192 | + | ||
| 193 | + | The [[Oxygene]] Object Pascal compiler is available from [RemObjects Software](http://www.remobjects.com). | |
| 194 | + | ||
| 195 | + | With both .NET and Mono, you can compile TestLib.pas as follows: | |
| 196 | + | ||
| 197 | + | [pathto]oxygene TestLib.pas | |
| 198 | + | ||
| 199 | + | With both .NET and Mono, this will create file testlib.exe. | |
| 200 | + | ||
| 201 | + | ## Running the simple app | |
| 202 | + | ||
| 203 | + | With .NET, just run the compiled app as follows: | |
| 204 | + | ||
| 205 | + | testlib | |
| 206 | + | ||
| 207 | + | If .NET can find simplelib.dll, the app will output this to the console: | |
| 208 | + | ||
| 209 | + | Value is 123 | |
| 210 | + | Successor is 124 | |
| 211 | + | Predecessor is 122 | |
| 212 | + | ||
| 213 | + | With Mono, run the compiled app as follows: | |
| 214 | + | ||
| 215 | + | mono TestLib.exe | |
| 216 | + | ||
| 217 | + | Mono will also look for file simplelib.dll -- that's the reason why we renamed the compiled library above on Linux and macOS. | |
| 218 | + | ||
| 219 | + | Note that .NET/Mono ".exe" files don't contain actual native compiled code. Instead, they contain bytecode that is executed by the .NET/Mono runtime. As a result, you can take a .NET .exe file created on Windows and run it with Mono on macOS and Linux (and vice versa) without recompiling it. You will have to recompile your Pascal libraries for each platform you want to support. | |
| 220 | + | ||
| 221 | + | ## When to use native libraries with .NET/Mono | |
| 222 | + | ||
| 223 | + | Why not just code everything in VB.NET or C#? There are several situations where this might not be a good idea: | |
| 224 | + | ||
| 225 | + | - When you have a large or complicated Pascal codebase that you don't want to rewrite (and re-debug) in C#. | |
| 226 | + | - When you have native libraries that need to be used by both native and .NET/Mono apps. | |
| 227 | + | - When there are performance reasons (presumably compiled FPC or Delphi native code is faster than the equivalent compiled bytecode run under .NET/Mono). | |
| 228 | + | - When you need low-level operating system access not available easily with .NET/Mono. | |
| 229 | + | - When you need to quickly develop code in a language you're more proficient in for a multi-developer .NET/Mono project. | |
| 230 | + | ||
| 231 | + | ## Mono tips | |
| 232 | + | ||
| 233 | + | **1.** Since Visual Basic is pretty foreign to Linux and macOS, you'll probably want to develop .NET and Mono apps using C#, which is similar to C++ (and Object Pascal). Or use the Oxygene compiler and develop your .NET and Mono apps with familiar Object Pascal. | |
| 234 | + | ||
| 235 | + | **2.** Mono on macOS includes a script for uninstalling it. You can run this script before installing a newer version of Mono: | |
| 236 | + | ||
| 237 | + | - In Finder, navigate to /Library/Receipts. | |
| 238 | + | - Ctrl+click on MonoFramework.pkg and choose Show Package Contents from the popup menu. | |
| 239 | + | - Double-click the Contents folder. | |
| 240 | + | - Double-click the Resources folder. | |
| 241 | + | - Drag a copy of uninstallMono.sh to the desktop. | |
| 242 | + | - Open a Terminal window. | |
| 243 | + | - cd desktop | |
| 244 | + | - sudo ./uninstallMono.sh and enter your password. | |
| 245 | + | ||
| 246 | + | **3**. In general, don't use any Lazarus units in your libraries. Libraries usually don't have a user interface. Confine your use to Free Pascal units like System, SysUtils, StrUtils, DateUtils, Classes, Variants and Math. | |
| 247 | + | ||
| 248 | + | **4.** If you need a user interface to go with your library, program it as a Windows Forms application. Here's a simple form example. Copy and save this code to file MyForm.cs. | |
| 249 | + | ||
| 250 | + | ``` csharp | |
| 251 | + | using System; | |
| 252 | + | using System.Windows.Forms; | |
| 253 | + | ||
| 254 | + | public class MyForm: Form | |
| 255 | + | { | |
| 256 | + | public MyForm () | |
| 257 | + | { | |
| 258 | + | this.Text = "A .NET/Mono Windows Forms App"; | |
| 259 | + | this.Width = 400; | |
| 260 | + | ||
| 261 | + | Button b = new Button(); | |
| 262 | + | b.Left = 15; | |
| 263 | + | b.Top = 20; | |
| 264 | + | b.Width = this.Width-30; | |
| 265 | + | b.Height = 30; | |
| 266 | + | b.Text = "Click Me"; | |
| 267 | + | b.Click += new EventHandler(button_Click); | |
| 268 | + | this.Controls.Add(b); | |
| 269 | + | } | |
| 270 | + | void button_Click(object sender, EventArgs e) | |
| 271 | + | { | |
| 272 | + | MessageBox.Show("Hello"); | |
| 273 | + | } | |
| 274 | + | } | |
| 275 | + | ||
| 276 | + | public class MyApp | |
| 277 | + | { | |
| 278 | + | public static void Main() | |
| 279 | + | { | |
| 280 | + | MyForm aForm = new MyForm(); | |
| 281 | + | Application.Run(aForm); | |
| 282 | + | } | |
| 283 | + | } | |
| 284 | + | ``` | |
| 285 | + | ||
| 286 | + | Compile this code on Mono as follows: | |
| 287 | + | ||
| 288 | + | mcs -r:System.Windows.Forms MyForm.cs | |
| 289 | + | ||
| 290 | + | Run the app as follows: | |
| 291 | + | ||
| 292 | + | mono MyForm.exe | |
| 293 | + | ||
| 294 | + | On macOS, you can also create a normal app bundle using the macpack utility: | |
| 295 | + | ||
| 296 | + | macpack -n MyForm -m winforms MyForm.exe | |
| 297 | + | ||
| 298 | + | This creates the MyForm.app bundle, which can be double-clicked, dropped onto the Dock, etc. | |
| 299 | + | ||
| 300 | + | **5.** On Linux and macOS, Mono now includes MonoDevelop, a simple IDE for editing and compiling code. | |
| 301 | + | ||
| 302 | + | **6.** If you have any questions or comments about using Pascal libraries with .NET or Mono, please post them to the Lazarus forum. | |
| 303 | + | ||
| 304 | + | --- | |
| 305 | + | *From the Free Pascal wiki, page "Using Pascal Libraries with .NET and Mono", as it stood on 2020-05-12 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
