Jump to content

Leadwerks and C#

IgorBgz90

204 views

Actually it is very simple. As usual, we need to export the functions we are interested in to the Dynamic Library (DLL), and then import them into our C# program. Let's start.

C++ DLL
The first thing we need is to create a project in C++ for our future DLL, and of course configure it for the Leadwerks engine

Configuration for Release
Right-click on the project and select Property. In the window that appears, go to the tab "С/C++" / "General".

Copy and Paste to a "Additional Include Directories" this:

$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dgCore;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dgNewton;$(LeadwerksHeaderPath)\Libraries\libvorbis\include;$(LeadwerksHeaderPath)\Libraries\libogg\include;$(LeadwerksHeaderPath)\Libraries\openssl\include;$(LeadwerksHeaderPath);$(LeadwerksHeaderPath)\Libraries\VHACD\src\VHACD_Lib\inc;$(LeadwerksHeaderPath)\Libraries\glslang;$(LeadwerksHeaderPath)\Libraries\freetype-2.4.7\include;$(LeadwerksHeaderPath)\Libraries\OpenAL\include;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dMath;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dgTimeTracker;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dContainers;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dCustomJoints;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\RecastDemo\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\DetourCrowd\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\DetourTileCache\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\DebugUtils\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\Recast\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\Detour\Include;$(LeadwerksHeaderPath)\Libraries\tolua++-1.0.93\include;$(LeadwerksHeaderPath)\Libraries/lua-5.1.4;$(LeadwerksHeaderPath)\Libraries/glew-1.6.0/include/GL;$(LeadwerksHeaderPath)\Libraries\glew-1.6.0\include;$(LeadwerksHeaderPath)\Libraries\enet-1.3.1\include;$(LeadwerksHeaderPath)\Libraries\zlib-1.2.5;$(LeadwerksHeaderPath)\Libraries\freetype-2.4.3\include;%(AdditionalIncludeDirectories)

Go to "Preprocessor", in "Preprocessor Definitions" copy and paste this:

WIN32;NDEBUG;LEADWERKS_EXPORTS;_WINDOWS;_USRDLL;PSAPI_VERSION=1;__STEAM__;_CUSTOM_JOINTS_STATIC_LIB;FT2_BUILD_LIBRARY;LEADWERKS_3_1;DG_DISABLE_ASSERT;WINDOWS;OS_WINDOWS;OPENGL;PLATFORM_WINDOWS;_WIN_32_VER;_NEWTON_USE_LIB;PTW32_STATIC_LIB;PTW32_BUILD;_NEWTON_STATIC_LIB;_LIB;DG_USE_NORMAL_PRIORITY_THREAD;GLEW_STATIC;_STATICLIB;%(PreprocessorDefinitions)

Go to "Code Generation". Set these values:
Enable Minimal Rebuild = Yes(/Gm)
Runtime Library = Multi-threaded (/MT)
Enable Function-Level-Linking = Yes (/Gy)

Go to "Precompiled Header" and set: "Not Using Precompiled Headers"

Now go to the "Linker" / "General" tab, then in "Additional Library Directories" copy and paste this:

$(LeadwerksLibPath)\Windows\x86;$(LeadwerksLibPath)\Windows\x86\Release;C:/Leadwerks\Engine\Source\Libraries\OpenAL/libs/Win32/EFX-Util_MT;C:/Leadwerks\Engine\Source\Libraries\OpenAL/libs/Win32;%(AdditionalLibraryDirectories)

Go to "Input", in "Additional Dependencies" copy and paste this:

newton.lib;dContainers.lib;dCustomJoints.lib;libcryptoMT.lib;libsslMT.lib;Rpcrt4.lib;crypt32.lib;libcurl.lib;Leadwerks.lib;msimg32.lib;lua51.lib;steam_api.lib;OpenAL32.lib;ws2_32.lib;libovr.lib;newton.lib;dContainers.lib;dCustomJoints.lib;OpenGL32.lib;Glu32.lib;winmm.lib;Psapi.lib;%(AdditionalDependencies)

 

Propery Sheet
Right-click on the project and select "Add \ New Item \ Property Sheets \ Property Sheet" click Add button.
Open it and paste this text:
Warning! Keep in mind that the paths may be different, check them out.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros">
    <LeadwerksHeaderPath>C:/Program Files (x86)/Steam/steamapps/common/Leadwerks\Include</LeadwerksHeaderPath>
    <LeadwerksLibPath>C:/Program Files (x86)/Steam/steamapps/common/Leadwerks\Library</LeadwerksLibPath>
  </PropertyGroup>
  <PropertyGroup />
  <ItemDefinitionGroup />
  <ItemGroup>
    <BuildMacro Include="LeadwerksHeaderPath">
      <Value>$(LeadwerksHeaderPath)</Value>
      <EnvironmentVariable>true</EnvironmentVariable>
    </BuildMacro>
    <BuildMacro Include="LeadwerksLibPath">
      <Value>$(LeadwerksLibPath)</Value>
      <EnvironmentVariable>true</EnvironmentVariable>
    </BuildMacro>
  </ItemGroup>
</Project>

If all the paths match, you can import it into the project. Go to menu "VIEW / Other Windows / Property Manager"
Click button "Add Existing Property Sheet".

On this with the project settings everything =).
Now we can go directly to the code. This is part of the functions required for a simple program; you can add other functions yourself.

// Leadwerks.cpp : Defines the exported functions for the DLL application.
//

#include "Leadwerks.h"
using namespace Leadwerks;

#define EXPORT extern "C" __declspec(dllexport)

// ----------------------------------------------------------------------------------
// SYSTEM
// ----------------------------------------------------------------------------------
EXPORT void LE_System_Initialize() { System::Initialize(); }
EXPORT void LE_System_Shutdown() { System::Shutdown(); }
EXPORT void LE_System_SetAppName(const char* name) { System::AppName = name; }
EXPORT void LE_System_SetAppPath(const char* path) { System::AppPath = path; }

// ----------------------------------------------------------------------------------
// WINDOW
// ----------------------------------------------------------------------------------
EXPORT Window* LE_Window_Create(HWND hwnd) { return Window::Create(hwnd); }
EXPORT void LE_Window_Free(Window* window) { delete window; }
EXPORT bool LE_Window_Closed(Window* window) { return window->Closed(); }
EXPORT bool LE_Window_KeyHit(Window* window, int keycode) { return window->KeyHit(keycode); }

// ----------------------------------------------------------------------------------
// CONTEXT
// ----------------------------------------------------------------------------------
EXPORT Context* LE_Context_Create(Window* window) { return Context::Create(window); }
EXPORT void LE_Context_Free(Context* context) { delete context; }
EXPORT void LE_Context_Sync(Context* context, bool sync) { context->Sync(sync); }

// ----------------------------------------------------------------------------------
// WORLD
// ----------------------------------------------------------------------------------
EXPORT World* LE_World_Create() { return World::Create(); }
EXPORT void LE_World_Free(World* world) { delete world; }
EXPORT void LE_World_Update(World* world) { world->Update(); }
EXPORT void LE_World_Render(World* world) { world->Render(); }

// ----------------------------------------------------------------------------------
// CAMERA
// ----------------------------------------------------------------------------------
EXPORT Camera* LE_Camera_Create() { return Camera::Create(); }
EXPORT void LE_Camera_SetClearColor(Camera* camera, float r, float g, float b, float a) { camera->SetClearColor(r, g, b, a); }

Press "Ctrl+Shift+B"

C#
After a successful build, let's move on to C#.
Let's create a new project "Windows Forms App"

Here is the program code for an example:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LETest
{
    public partial class Form1 : Form
    {
        const string LEDLL = "Leadwerks.dll";
        [DllImport(LEDLL)]
        public static extern void LE_System_Initialize();
        [DllImport(LEDLL)]
        public static extern void LE_System_Shutdown();
        [DllImport(LEDLL)]
        public static extern void LE_System_SetAppName(String name);
        [DllImport(LEDLL)]
        public static extern void LE_System_SetAppPath(String path);

        IntPtr window;
        [DllImport(LEDLL)]
        public static extern IntPtr LE_Window_Create(IntPtr hwnd);
        [DllImport(LEDLL)]
        public static extern void LE_Window_Free(IntPtr window);

        IntPtr context;
        [DllImport(LEDLL)]
        public static extern IntPtr LE_Context_Create(IntPtr window);
        [DllImport(LEDLL)]
        public static extern void LE_Context_Free(IntPtr context);
        [DllImport(LEDLL)]
        public static extern void LE_Context_Sync(IntPtr context, bool sync);

        IntPtr world;
        [DllImport(LEDLL)]
        public static extern IntPtr LE_World_Create();
        [DllImport(LEDLL)]
        public static extern void LE_World_Free(IntPtr world);
        [DllImport(LEDLL)]
        public static extern void LE_World_Update(IntPtr world);
        [DllImport(LEDLL)]
        public static extern void LE_World_Render(IntPtr world);

        IntPtr camera;
        [DllImport(LEDLL)]
        public static extern IntPtr LE_Camera_Create();
        [DllImport(LEDLL)]
        public static extern void LE_Camera_SetClearColor(IntPtr camera, float r, float g, float b, float a);

        Thread loopThread;
        bool isAppWork;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            isAppWork = true;
            loopThread = new Thread(EngineUpdate);
            loopThread.IsBackground = true;
            loopThread.Start();
        }

        private void EngineUpdate()
        {
            //Initialize
            LE_System_Initialize();
            window = LE_Window_Create(panel1.Handle);
            context = LE_Context_Create(window);
            world = LE_World_Create();
            camera = LE_Camera_Create();
            LE_Camera_SetClearColor(camera, 0.0f, 0.0f, 1.0f, 1.0f);

            //Main loop
            while (isAppWork)
            {
                LE_World_Update(world);
                LE_World_Render(world);
                LE_Context_Sync(context, true);
            }

            //Free
            LE_World_Free(world);
            LE_Context_Free(context);
            LE_Window_Free(window);
            LE_System_Shutdown();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            //isAppWork = false;
            loopThread.Abort();
            loopThread = null;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            isAppWork = false;
        }
    }
}

Copy you'r "DLL" to bin folder.


UPDATE:
Copy these file from you'r leadwekrs project to c# bin folder:
Shaders folder
dContainers.dll
dContainers_d.dll
dCustomJoints.dll
dCustomJoints_d.dll
libcurl.dll
lua51.dll
newton.dll
newton_d.dll
openvr_api.dll
steam_api.dll

  • Like 2


16 Comments


Recommended Comments

11 minutes ago, reepblue said:

Seems like a lot of work, but very cool. 

Thanks. In fact, everything is very simple😏

Share this comment


Link to comment
12 minutes ago, Iris3D Games said:

One question: Are compilation times equal to c++?

What do you mean?
C# uses functions from the dynamic library, and she does not need to compile engine functions.

Update:
Compile time is equal to the size of your C# project🙂

Share this comment


Link to comment

looks interesting i will have a play later as am learning c# at the moment so this be fun to try  

Share this comment


Link to comment
3 hours ago, IgorBgz90 said:

Thanks. In fact, everything is very simple😏

Not a C# user, so bare with me.

From the looks of it, it seems you need to export every fiction call via a wrapper function. Wouldn't that just be silly in real use cases? I'm sure there is a better way. 

Still neat tho.

Share this comment


Link to comment
1 minute ago, reepblue said:

Not a C# user, so bare with me.

From the looks of it, it seems you need to export every fiction call via a wrapper function. Wouldn't that just be silly in real use cases? I'm sure there is a better way. 

Still neat tho.

Oh yeah! Not the best option, but the other I do not know🤔

Share this comment


Link to comment
9 minutes ago, carlb said:

looks interesting i will have a play later as am learning c# at the moment so this be fun to try  

Thanks. But keep in mind that there is only the initialization of the rendering engine, and that's all. If you want more, you will have to export other engine functions😎

Share this comment


Link to comment

Hi, 

I'm working on a similar project and i'm currently preparing a github repo for my Wrapper. It will still take some time and cleanup until it is up, but it will provide full access to the API (same as in lua + some extras). Meanwhile you may take a look http://www.swig.org/ or https://github.com/mono/CppSharp for generating bridges between Cpp and CSharp. The nicer way would be CppSharp, but i can't get the parser to work, so i will stick with swig. 

Currently a small sample looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Leadwerks.Core;

namespace Leadwerks.Test
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Entity> entities = new List<Entity>();

            Window window = Window.Create();
            Context context = Context.Create(window);
            World world = World.Create();
            world.SetLightQuality(2);
            Camera camera = Camera.Create();
            camera.SetRotation(35f, 0f, 0f);
            camera.Move(0f, 0f, -6f);
            Light light = DirectionalLight.Create();
            light.SetRotation(35, 35, 0);
            entities.Add(light);
            Model model = null;

            model = Model.Box();
            model.SetColor(1.0f, 0.0f, 0.0f);
            model.SetPosition(-4, 1, 0);
            entities.Add(model);

            model = Model.Cylinder();
            model.SetColor(0.0f, 1.0f, 0.0f);
            model.SetPosition(-2, 1, 0);
            entities.Add(model);

            model = Model.Cone();
            model.SetColor(0.0f, 0.0f, 1.0f);
            model.SetPosition(0, 1, 0);
            entities.Add(model);

            model = Model.Sphere();
            model.SetColor(0.0f, 1.0f, 1.0f);
            model.SetPosition(2, 1, 0);
            entities.Add(model);

            model = Model.Create();
            model.SetColor(1.0f, 0.0f, 1.0f);
            model.SetPosition(4, 1, 0);
            entities.Add(model);

            Surface surface = model.AddSurface();
            surface.AddVertex(-0.5f, -0.5f, 0, 0, 0, -1);
            surface.AddVertex(0.5f, -0.5f, 0, 0, 0, -1);
            surface.AddVertex(0.5f, 0.5f, 0, 0, 0, -1);
            surface.AddVertex(-0.5f, 0.5f, 0, 0, 0, -1);
            surface.AddTriangle(2, 1, 0);
            surface.AddTriangle(0, 3, 2);
            surface.UpdateAABB();
            model.UpdateAABB(Entity.LocalAABB | Entity.GlobalAABB);
            model.Move(0,0.25f,0);
            entities.Add(model);

            Terrain terrain = Terrain.Create(512);
           
            while (true)
            {
                if (window.KeyDown(Key.Escape)) return;

                foreach(var entity in entities)
                    entity.Turn(0.1f,0.1f,0.1f);

                Time.Update();
                world.Update();
                world.Render();

                context.DrawText("Hello World", 20, 20, 100, 100, 1);
                context.DrawText("Hello World", 20, 40);
                context.Sync();
            }
        }
    }
}

 

  • Like 1

Share this comment


Link to comment

A small hint: 

if you take a closer look into the Include directory you will find this: DLLExport.h

This file already contains a lot of exports yu may use as it is the dll code used for the editor.

  • Like 1

Share this comment


Link to comment

I think in the new engine especially, C# will be a viable option.

  • Your game code gets 16 milliseconds to execute independently from the rendering thread so there are no worries about performance.
  • When using the auto keyword for variables, the only different between C# and C++ code will be to replace "->" with ".".

Now in the new engine we are relaying more on standard containers, because my new Lua binding code handles them better. There is no CountChildren() or CountVertices(), there is just a vector of objects that contains those things:

for (int n=0; n < entity->kids.size(); ++n)
{
	entity->kids[n]->Hide();
}

This says support is experimental. Maybe inquire with the developer and if it will work in the near future I can use vectors more and avoid unsupported container types:
https://github.com/mono/CppSharp/blob/master/docs/UsersManual.md#containers

  • Like 1

Share this comment


Link to comment
7 hours ago, klepto2 said:

A small hint: 

if you take a closer look into the Include directory you will find this: DLLExport.h

This file already contains a lot of exports yu may use as it is the dll code used for the editor.

Thanks! As it did not pay attention to him 🙂

Share this comment


Link to comment
12 minutes ago, IgorBgz90 said:

This file already contains a lot of exports yu may use as it is the dll code used for the editor.

It turns out that there is no need to export functions, they already exist in the file "Leadwerks.dll"?)

Share this comment


Link to comment

It would be pretty sweet to see C# get some love in LE. I know many have tried in the past and it just didn't work out well but C# is a great language!

  • Like 2

Share this comment


Link to comment
4 hours ago, Rick said:

It would be pretty sweet to see C# get some love in LE. I know many have tried in the past and it just didn't work out well but C# is a great language!

I don't see any technical problems, it's just a matter of maintaining a lot of wrapper code, which is why an automated approach would be best.

I took a look at SWIG but could not immediately make heads or tails of it.

  • Like 3

Share this comment


Link to comment
On 6/1/2019 at 12:23 AM, Josh said:

 I don't see any technical problems, it's just a matter of maintaining a lot of wrapper code, which is why an automated approach would be best.

 I took a look at SWIG but could not immediately make heads or tails of it.

Why not make it a plugin for Turbo or would that not be feasible? 

Share this comment


Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...