Building a custom .dll file to use with RPG Maker...

Started by Heretic86, June 03, 2016, 05:55:07 pm

Previous topic - Next topic

Heretic86

Since I'm currently stuck on my Light Script, I thought I would expand my skillset and do it in a way that helps everyone interested.  Im going with C++ for this.  Getting a compiler was a pain, but I finally got one.  Visual C++ 2015.

So what Ive done so far is this.  New Win32 project, dll, and empty project.

Header.h
#define DLLEXPORT __declspec(dllexport)

extern "C" DLLEXPORT int hello(int);


Source.cpp
#include "header.h"

int hello(int arg)
{
arg++;
return arg;
}


RPG Maker Script (Ruby):
class Game_Character
  Hello =  Win32API.new("rotate.dll", "hello", "I", "I")
 
  def hello(arg)
    print Hello.call(arg)
  end
end


Event Move Route -> Script:
$>script: hello(5)

---

This is as basic as it gets.

Im completely unfamiliar with C++ headers.  So if my next step is to interact with a Bitmap, where do I go from there?  Also assuming that since Im working with Transparency that is had from .png files, the term "Bitmap" is a bit misleading.

I tried looking at a few examples online, but it doesnt make sense yet.  Im stuck at exporting the function in DLLEXPORT, but basically I'll make a function that does image rotation using some of the prebuilt methods, (GDI?) and return the Image.  Or, do I need to use pointers and make changes to the memory addresses on the fly?  Wouldnt that affect the bitmap stored in RPG::Cache?

extern "C" DLLEXPORT HBITMAP rotateBitmap(HBITMAP, float)

I know I need another header in there so I can declare what a HBITMAP is...

What is my next step?

*feels very stupid*
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)

KK20

It's pretty tricky if you don't have any resources to rely on. Perhaps you should take a look at your old topic again?
http://forum.chaos-project.com/index.php/topic,14127.0.html

The main important thing to get out of it is the bitmap structure:

typedef struct {
    DWORD flags;
    DWORD klass;
    void (*dmark) (void*);
    void (*dfree) (void*);
    BYTE *data; //B = 0, G = 1, R = 2, A = 3
} RGSSCOLOR;

typedef struct{
    DWORD unk1;
    DWORD unk2;
    BITMAPINFOHEADER *infoheader;
    RGSSCOLOR *firstRow;
    RGBQUAD *lastRow;
} RGSSBMINFO;

typedef struct{
    DWORD unk1;
    DWORD unk2;
    RGSSBMINFO *bminfo;
} BITMAPSTRUCT;

typedef struct{
    DWORD flags;
    DWORD klass;
    void (*dmark) (void*);
    void (*dfree) (void*);
    BITMAPSTRUCT *bm;
} RGSSBITMAP;

/*==========================================================================
** BlackAndWhite
Input:
- object : Address to the Bitmap object (i.e. bitmap.object_id )
----------------------------------------------------------------------------
Turns a bitmap image into a grayscale version. Uses the basic
implementation of grayscale conversion, that is (R+G+B)/3 applied to
each pixel's RGB values.
Kept to explain how bitmap manipulation works in DLLs.
==========================================================================*/

extern "C" __declspec (dllexport) bool BlackAndWhite(long object){
    RGSSBMINFO *bitmap = ((RGSSBITMAP*) (object<<1)) -> bm -> bminfo;
    DWORD rowsize;
    DWORD width, height;
    LPBYTE row;
    long x, y;
    int shade;
    if(!bitmap) return false;
    width = bitmap -> infoheader -> biWidth;
    height = bitmap -> infoheader -> biHeight;
    rowsize = width * 4;
    row = (LPBYTE) (bitmap -> firstRow);
    for ( y = 0; y < (int) height; y++) {
        LPBYTE thisrow = row;
        for ( x = 0; x < (int) width; x++) {
            shade = ((thisrow[2] + thisrow[1] + thisrow[0]) / 3);
            thisrow[2] = shade;
            thisrow[1] = shade;
            thisrow[0] = shade;
            thisrow += 4;
        }
        row -= rowsize;
    }
    return true;
}

To use the code above, your ruby would look like

s = Sprite.new
s.bitmap = Bitmap.new #draw stuff to it or use RPG::Cache and load a graphic
dll = WinAPI32.new('NAME.dll', 'BlackAndWhite', 'L', 'I')
dll.call(s.bitmap.object_id)


I don't know if converting the Bitmap struct to something else, using a graphic library's method, and converting it back is possible (leaving that to Blizz since my C/++ is not that great). You're probably just going to have to suck it up and write the algorithm yourself.

And yeah, you're going to need to pass in both a source and destination bitmap as your arguments if you don't want to affect the cached bitmap.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Heretic86

Im fine with writing my own algorithm, but Im pretty sure one already exists in TransformRotate from a few mentions of stuff I've seen online using Graphics Device Interface for a hardware transform for speed.  Mostly, learning experience.

Now, question:

extern "C" __declspec (dllexport) bool BlackAndWhite(long object){
    RGSSBMINFO *bitmap = ((RGSSBITMAP*) (object<<1)) -> bm -> bminfo;

...


Exporting dll functions can be done at the same time as the function declaration?  Returning a bool?  Hmm, maybe it would be better to have void function_name(*bitmap, angle), then instead of returning a bitmap, just access the memory addresses via pointers?  Im not sure cuz I dont know anything about how the GDI works, or if it can be handled in the same memory space (RAM vs GPU Ram).  Here's what Im thinkin...

# Shallow Copy of Image
light_bmp = RPG::Cache.light(@light.file_name, @light.hue).clone
# Rotate Image if needed
light_bmp = Rotate.call(light_bmp, @light.angle) if @light.angle != 0
# Width and Height (changes with Rotation)
w, h = light_bmp.width, light_bmp.height

...

# Draw Image into the Shadowmap Sprite to create a Light Hole in Shadow
@shadowmap.bitmap.stretch_blt(rect, light_bmp, light_bmp.rect, opacity)


Think that could possibly work?
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)

KK20

If your rotate method is a void function, the light_bmp = is impossible. You are just accessing the bitmap color data directly using pointers. There's nothing to return as a result. The bool in my example is just there for cases where if you want to error check: TRUE if good, FALSE otherwise.

You have to pass in the Bitmap#object_id. This is the address to the bitmap object in memory.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Heretic86

Ooop, yep, thats true, void wouldnt give any value back.  Wasnt sure if it should or shouldnt provide a return value...
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)

orochii

I love when you people talk about these things, it's the reason why I'm in this community <3.

This feels me with determination.

Oh, just don't mind me, please continue with your conversation.

Blizzard

Quote from: Heretic86 on June 03, 2016, 08:52:56 pm
extern "C" __declspec (dllexport) bool BlackAndWhite(long object){
    RGSSBMINFO *bitmap = ((RGSSBITMAP*) (object<<1)) -> bm -> bminfo;

...


Exporting dll functions can be done at the same time as the function declaration?  Returning a bool?  Hmm, maybe it would be better to have void function_name(*bitmap, angle), then instead of returning a bitmap, just access the memory addresses via pointers?  Im not sure cuz I dont know anything about how the GDI works, or if it can be handled in the same memory space (RAM vs GPU Ram).  Here's what Im thinkin...


I just want to add that exporting in headers makes the most sense if you are building a DLL that's to be used by other C code. It's easier to just include a header in a depending library than write the function definition all over again. Since Ruby can't do that, you don't need that there and can go without headers. Also, Ruby can only use C DLLs, not C++ DLLs.




As for the format of the bitmap, it's bottom-row-first and the pixel format is BGRA LSB. You don't have to do decode that PNG in the C code, RGSS does that for you. That's why you load the Bitmap and then pass Bitmap#object_id to the DLL.

Feel free to take a look at how I implemented some Bitmap functionality in my XPA_Window implementation. It's similar to Bitmap#stretch_blt, but it overwrites the pixels and does linear interpolation rather than nearest neighbor interpolation. This is one of the important pieces:


unsigned char* srcData = (unsigned char*)srcBitmap->firstRow; // previously declared somewhere
...
ctl = &srcData[(_x0s[x] - _y0s[y] * srcWidth) * BPP];


If the top row was the first row in the data, the code should look like this:


ctl = &srcData[(_x0s[x] + _y0s[y] * srcWidth) * BPP];


But it's not. So I have to keep going backwards through the rows. The rows themselves are ok though.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

ForeverZer0

A couple of years ago I made a dll that performs all sorts of manipulations on bitmaps. Never did complete it totally, and it is written in C# with .NET Framework, but much of the methods could be easily translated to another language. You can at least see examples of how the pointers to the bitmaps are handled, etc.

https://sourceforge.net/projects/rpgnet/
I am done scripting for RMXP. I will likely not offer support for even my own scripts anymore, but feel free to ask on the forum, there are plenty of other talented scripters that can help you.

Heretic86

June 05, 2016, 06:57:30 pm #8 Last Edit: June 05, 2016, 06:59:17 pm by Heretic86
Okay, Im feelin REALLY dumb.

Using C++, and struggling on the basics, with Pointers.

Ruby Code:
class Game_Character
  # 4 Args - DLL File, DLL Function, Send Type, Return Type (ILPV - Int Long Pointer Void)
  Sub =  Win32API.new("RMDLL.dll", "sub", "P", "")
  // arg : C++ int
  def sub(arg)
    Sub.call(arg)
    print "after DLL ", arg
  end
end


C++ Code
#define DLLEXPORT __declspec(dllexport)
DLLEXPORT

extern "C" __declspec (dllexport) void sub(int *arg)
{
  *arg--;
}


I think I get the concept of Pointers where & is memory address and * is for value held in that memory, but apparently not.  In the Win32API, using "P" says Pointer and "" for returning void / null / nil, wouldnt the dll function modify the value held in the address of RMXP memory so after I make any modifications to it via the dll, why doesnt it show up in RMXP?  Im apparently not getting the correct differences between "int* arg", "int *arg", and "*arg--"...  What is malfunctioning in my melting brain pan?

Also tried some stuff using strings, returning a pointer from the dll with return std::string "Hello World"; and getting the string with some funky characters on the left after .unpack("A*) which only sort of seems to work because I cant clone or copy the value to mess with the string later.  What is going on there?

(Also, asking these questions as I am sure anyone else interested in making a custom dll will probably have the same issues, so please keep in mind that more people than just us are looking at this when answering...)
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)

Heretic86

June 10, 2016, 07:58:43 pm #9 Last Edit: June 10, 2016, 07:59:44 pm by Heretic86
Someone want to explain why this doesnt work?

char *p[20];
extern "C" __declspec (dllexport) void mk_str(char *p)
{
char txt[20];
txt[0] = 'H';
txt[1] = 'i';
p = txt;
}


class Game_Character
  Mk_Str = Win32API.new("RMDLL.dll", "mk_str", "P", "")

  def mk_str()
    @dll_var = "Foo"
    print "Before is ", @dll_var, "\nObject ID is ", @dll_var.object_id
    Mk_Str.call(@dll_var.object_id)
    print "After change is ", @dll_var
  end
end


Is anyone still following this thread?
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)

KK20

My guess is by doing @dll_var.object_id, you're returning the address to the String object. And by String object, I mean Ruby's class String, not the text itself. If you looked at some examples of Win32API being used in other people's scripts, you would have answered the question yourself.

Like from Custom Resolution or XPA Tilemap:

ini = Win32API.new('kernel32', 'GetPrivateProfileString','PPPPLP', 'L')
title = "\0" * 256
ini.call('Game', 'Title', '', title, 256, '.\\Game.ini')
title.delete!("\0")
@window = Win32API.new('user32', 'FindWindow', 'PP', 'I').call('RGSS Player', title)

A string is already a char array/pointer. There's no need to object_id it.

Also the char *p[20] is completely unnecessary. That's just basic C stuff. It would also be better if you just made the changes to your argument directly rather than creating another char pointer/array.

Quote(Also, asking these questions as I am sure anyone else interested in making a custom dll will probably have the same issues, so please keep in mind that more people than just us are looking at this when answering...)

This statement is redundant as this is what we have been doing this entire time. We can't dumb this down anymore for everyone to be able to make a DLL. If anyone can't follow along to what's being said here, you shouldn't be making a DLL in the first place.
QuoteIs anyone still following this thread?

Work sucks. Weekends are the best.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Blizzard

Agreed, char*p[20] is redundant.

Your code isn't working, because you are filling the data into a local string and then just overwriting the pointer you got from RMXP. You would have to use memcpy to make this work rather that p = txt. Also, you are missing the terminator. char p[20] does not initialize the memory, only allocate it. char p[20] = { 0 } would be the correct way to do it.

That being said, KK20 is right and you can directly manipulate the string you got from RMXP. And since char is A basic type, you don't need to pass object_id, but just the string itself like in his example.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

Heretic86

June 11, 2016, 04:10:26 am #12 Last Edit: June 11, 2016, 04:37:35 am by Heretic86
One would figure that for the number of attempts I've made to get this to work, I should have at least stumbled on a solution by now even if I didnt understand it.  This is why I didnt continue to learn the last time too.  Concept of a simple "Hello World".  Here DLL, this is my ruby var, lets change the text to "Hello World" and I cant even do something as simple that.  If I cant do that, I'll never be able to modify anything else using dll files. 

Fuck I feel retarded.  Got shit on by my neighbor.  2 full weeks of dog sitting full time 24 / 7, watering and mowing lawn and house plants for a whopping 60 bucks.  Doesnt help the mood at all.  Im seriously about this close to giving up on dll files and C / C++ completely.

Sorry, Im just in a really bad mood after this shit with my snake of a neighbor.
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)

Blizzard

Relax. You can always pick it up again tomorrow or later. You're smart, you'll eventually get it. It's just a matter of not giving up.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

Heretic86

Im still screwing around with this...

The structs and what not that KK20 pointed back to are somewhat confusitating.  One thing I do need to do is change the size of a bitmap passed to a dll, but I dont see much in the way of the structs and ->biWidth and ->biHeight arent doing it...  Any suggestions on how to change the size of an RGSS Bitmap?
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)

Blizzard

Better don't do that in a DLL, because you don't know how the data was allocated in the first place. Your best bet is to create a target Bitmap with the desired size and pass it to the DLL as well. Then just do all your operations in such as way that you write them to the target Bitmap rather than try to modify the original. I don't think you can resize Bitmap instances in RGSS either so this imposes itself as a valid solution.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

Heretic86

*downgrades IQ*

Okay, how the hell do I get a float / double in as an argument?
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)

Blizzard

If I remember correctly, you use F instead of L in the arg listing.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

KK20

F is not supported. My guess is you will have to pass in an array and modify it in the DLL.
Quote from: ThallionDarkshine on May 03, 2014, 07:13:21 pm
If anyone is interested, to pass arrays to a dll, you simply create a ruby array, and call the pack method to pack it into something that can be passed to a win32api method. Ex.
Code: ruby
Array.new(10) { 0.0 }.pack("f*")

When receiving an array value in the dll, you take it in as a pointer to whatever type you are sending in. For example, for the array from the ruby example, the cpp type would be
Code: cpp
float*



I think this is what you do:
Code: DLL

extern "C" __declspec (dllexport) void method(float * var)
{
var[0] = 2.0 * var[0];
}


Code: Ruby

myDLL = Win32API.new('dll','method','p','')
var = [1.0]
result = myDLL.call(var.pack("F*"))

print result.unpack("F*")[0] #should be 2.0

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Heretic86

@Blizz or Satan or Grapist or whatever the hell name you come up with this week...  :D

Using "F" for Float didnt work.  Some googling says there are only 4 types of args for the Win32api calls:
I - int
L - long
P - pointer (usually string?)
V - void

KK20's stuff did work however.  Now at least I can monitor some values from inside the dll file.  Im passing an extra argument for a MessageBoxA that displays values I want to see from inside the DLL file, which is how I was finding I had a loss of data thru the float argument.

Ruby
showMsg = (Input.press?(Input::SHIFT)) ? 1 : 0


C++
  if (showMsg == 1)
    {
      std::string myStr = std::to_string(angle);
      MessageBoxA(hWnd, myStr.c_str(), "DLL Message", MB_OK);
    }


Then I got fucking LPCWSTR crap from standard MessageBox and learned about MessageBoxA by stumbling upon it.  I officially hate C++ with a passion.  I think once I am done doing with it the stuff I need, Im gonna put it under a pile of dead babies in a trash can and burn it.
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)