Jump to content

Leadwerks and C#

IgorBgz90

272 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.

  • Blog Entries

    • By Haydenmango in Snowboarding Development Blog 6
      So I've been researching snowboarding lately to get an idea of what animations and mechanics I need to create for my game.  I have learned lots of interesting things since I've only seen snow once or twice in my entire life and have never even tried snowboarding or any other board sports (skateboarding, surfing, etc.) for that matter.
       
      Snowboarding tricks are quite interesting as they are mostly derived from skateboarding.  Snowboarding tricks pay homage to their equivalent skating tricks by sharing many concepts and names.  For example basic grabs in snowboarding share the same concepts and names as skateboarding: indy, mute, method, stalefish, nosegrab, and tailgrab.  Something interesting to note is in snowboarding you can grab Tindy or Tailfish but this is considered poor form since these grabs can't be done on a skateboard (due to the board not being attached to the skaters feet) and grabbing these areas is generally something a novice snowboarder does when failing or "half-assing" a normal grab.  Check out this diagram to see how grabs work -
       
       
      So, after reading lots of text descriptions for tricks I was still confused by what all these terms meant and how they were actually applied.  So my next step was to look up these tricks actually being done and I found some really cool videos showing off how to do various tricks.  This video in particular is the best reference material I've found as it contains nearly every trick back to back with labeled names and some tweaks -
       
      Sadly my rigged model doesn't handle leg animations with the snowboard that well so I can't animate as many tricks as I want to.  Regardless there will still be around 15 total grab/air tricks in the game.  Now it's time for me to stop procrastinating and start animating!  
    • By jen in jen's Blog 3
      I thought I would share my experience on this; if you're working on Multiplayer, you will need to protect your packets. The solution is simple, let's go through how we can achieve this by implementing what Valve calls "challenge codes". (Some reading on the topic from Valve here: https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol#Challenge_response).
      Disclaimer: this doesn't cover other security techniques like authoritative server or encryption.
      So, I've worked on Border Recon last year (I think) and I needed a way to protect my server/client packets. There was no need for me to re-invent the wheel, I just had to copy what Valve has had for a  long time - challenge  codes.
      The idea behind challenge codes is similar to Captcha, but not exactly. Think of it like this: for every packet submitted to the server, it must be verified - how? By requiring the client to solve challenges our server provides.
      To implement this we need to have the following:
      A randomised formula in the server i.e.: a = b * c / d + e or a = b / c + d - e, be creative - it can be any combination of basic arithmetic or some fancy logic you like and can be however long as you want - do consider that the longer the formula, the more work your server has to do to make the computation.  Copy the same formula to the client. A random number generator.  So the idea here is:
      (Server) Generate a random number (see 3 above) of which the result would become the challenge code, (Server) run it through our formula and record the result. (Client) And then, we hand over the challenge code to the client to solve (an authentic client would have the same formula implemented in its program as we have on the server). For every packet received from the player, a new challenge code is created (and the player is notified of this change by the server in response). For every other packet, a new challenge code is created. (Client) Every packet sent to the server by the client must have a challenge code and its answer embedded.  (Server receives the packet) Run the challenge code again to our formula and compare the result to the answer embedded on the client's packet. (Server) If the answers are different, reject the packet, no changes to the player's state. The advantage(s) of this strategy in terms of achieving the protection we need to secure our server:
      - For every packet sent, new challenge code is created. Typically, game clients (especially FPS) will update its state in a matter of ms so even if a cheater is successful at sniffing the answer to a challenge code it would be invalidated almost instantaneously. 
      - Lightweight solution. No encryption needed. 
      Disadvantage(s):
      - The formula to answering the challenge code is embedded to the client, a cheater can de-compile the client and uncover the formula. Luckily, we have other anti-cheat solutions for that; you can implement another anti-cheat solution i.e. checking file checksums to verify the integrity of your game files and more (there are third-party anti cheat solutions out there that you can use to protect your game files).
       
       
       
    • By Josh in Josh's Dev Blog 4
      New commands in Turbo Engine will add better support for multiple monitors. The new Display class lets you iterate through all your monitors:
      for (int n = 0; n < CountDisplays(); ++n) { auto display = GetDisplay(n); Print(display->GetPosition()); //monitor XY coordinates Print(display->GetSize()); //monitor size Print(display->GetScale()); //DPI scaling } The CreateWindow() function now takes a parameter for the monitor to create the window on / relative to.
      auto display = GetDisplay(0); Vec2 scale = display->GetScale(); auto window = CreateWindow(display, "My Game", 0, 0, 1280.0 * scale.x, 720.0 * scale.y, WINDOW_TITLEBAR | WINDOW_RESIZABLE); The WINDOW_CENTER style can be used to center the window on one display.
      You can use GetDisplay(DISPLAY_PRIMARY) to retrieve the main display. This will be the same as GetDisplay(0) on systems with only one monitor.
×
×
  • Create New...