Beef DLL Creation & Research
Beef language is still in heavy development. Anything can change at any time in Beef language and thus this document may not be valid anymore!
Requirements
- Latest BeefIDE (nightly)
- DebugView++
About Beef
Beef is an open source performance-oriented compiled programming language. Basically it's C language with C# like syntax. OOP paradigm also can be used for developing applications since in Beef you can create custom reference types (Objects) and combine it with polymorphism. Reflection is also possible. Its quite fast and its fun to work with. More info can be found here -> https://www.beeflang.org/
Beef DLL and DllMain
So first we need to create DLL project in Beef IDE. Go to File -> New -> New Project and choose Dynamic Library.
Now we need to add new class called Program. Like this:
namespace DLLResearch
{
public static class Program
{
}
}
So... Normally when you are creating DLL (for example in C/++) you define DllMain method which is gonna be called when a process is going to try and load our library using, for example, LoadLibrary function from Win32 API. However, this is not possible in Beef since DllMain is already handled by Beef Runtime.
So what we gonna do now? 🤔 It turns out that the solution is very simple. Only thing we need to do is write a public static constructor and deconstructor for class Program.
using System.Diagnostics;
using System;
namespace DLLResearch
{
[AlwaysInclude] // <- We need this to include our class in DLL, Normally you dont need this
public static class Program
{
public static this() // <- This will gets called when our library gets loaded
{
Debug.WriteLine("Hello From Beef DLL :3");
}
public static ~this() // <- This will gets called when our library gets unloaded
{
Debug.WriteLine("See ya next time my friend :)");
}
}
}
So lets compile it and inject it into some process. I am gonna use notepad.
As you can see, when we inject our library into notepad we can see this in DebugView++.
Our constructor get called and we printed first message. So lets try to unload our library and see what happens.
Looks like our deconstructor gets called.
Note: Our deconstructor also gets called when process, where our library is loaded, gonna exit.
Exporting methods
So what if we want to export some methods from our DLL? Well... Thats a really simple task. Just write your method and throw ExportAttribute
on it like this:
[Export]
public static int32 MyAmazingMethod(int32 a, int32 b)
{
return a + b;
}
This will export your method using Beef style mangling. Ez ...
But what if you want to use your exported method in some app using C-style API? Then you need to add CLinkAttribute
. That will export your method using C-style mangling.
[Export, CLink]
public static int32 MyAmazingMethod(int32 a, int32 b)
{
return a + b;
}
See? It's that easy!
In case we need C++ mangling we can use LinkNameAttribute
. You can use this attribute to specify link name for importing methods from another library or to specify mangling style for exported method. Actually you don't need CLinkAttribute
at all. You can use LinkName(.C)
instead!
[Export, LinkName(.CPP)]
public static int32 MyAmazingMethod(int32 a, int32 b)
{
return a + b;
}
As you can see, calling convention __cdecl
is used by default. If you want to change that, you need to use CallingConventionAttribute
. For example, this is how we export our method using __stdcall
calling convention ...
[Export, LinkName(.CPP), CallingConvention(.Stdcall)]
public static int32 MyAmazingMethod(int32 a, int32 b)
{
return a + b;
}
Note: That works only when we are targeting x86 platform
How to work with threads
WARNING! Due to a bug in previous version of Beef, this works only in version 0.42.6 (Nightly 09/02/2020) and newer!
So ... what if we want to use threads? How can we start a thread on DLL loading and how can we terminate thread on DLL unloading? We also need to make sure that the process, where our DLL is loaded, will exit gracefully.
Lets add one thread called myThread
and initialize it, so it will run our method called ThreadMethod
.
using System.Diagnostics;
using System;
using System.Threading;
namespace DLLResearch
{
[AlwaysInclude]
public static class Program
{
public static Thread myThread = new Thread(new () => ThreadMethod());
public static this()
{
Debug.WriteLine("Hello From Beef DLL :3");
}
public static ~this()
{
Debug.WriteLine("See ya next time my friend :)");
}
public static void ThreadMethod()
{
//...
}
}
}
Right now, it will just initialize new thread and nothing else. Let's say that we want our thread periodically print some message. Our ThreadMethod
will gonna look like this:
public static void ThreadMethod()
{
Debug.WriteLine("ThreadMethod started.");
var i = 0;
while(true)
{
Debug.WriteLine("Hello from Beef DLL. Counter: {}", i++);
Thread.Sleep(500); // Wait 500 ms
}
Debug.WriteLine("ThreadMethod ended.");
}
But this code has one big problem. In this case, our DLL will not be unloaded from a process, because ThreadMethod
will never end! We need some form of signalization that will notify our thread that we want to exit. How we are gonna do that? Simply declare new static boolean called exit
. And replace while(true)
with while(!exit)
. That's it!
Last thing we need to do is start myThread
inside contructor and terminate it in decontructor:
using System.Diagnostics;
using System;
using System.Threading;
namespace DLLResearch
{
[AlwaysInclude] // <- We need this to include our class
public static class Program
{
public static Thread myThread = new Thread(new () => ThreadMethod());
public static bool exit = false;
// This will gets called when our library gets loaded
public static this()
{
myThread.Start(); // <- Start our thread
Debug.WriteLine("Hello From Beef DLL :3");
}
// This will gets called when our library gets unloaded
public static ~this()
{
exit = true; // Notify we want to exit
myThread.Join(500); // Wait max 500 ms for thread to finish
Debug.WriteLine("See ya next time my friend :)");
}
public static void ThreadMethod()
{
Debug.WriteLine("ThreadMethod started.");
var i = 0;
while(!exit)
{
Debug.WriteLine("Hello from Beef DLL. Counter: {}", i++);
Thread.Sleep(500); // Wait 500 ms
}
Debug.WriteLine("ThreadMethod ended.");
}
}
}
Compile it and inject your DLL into some process (notepad in my case) and after some time unload it.
And it works! Working on DLL using Beef is fun and really simple. Next time i am gonna try to create simple plugin system for DLLs made in Beef.
Comments