How to use .dll libs created by rust

I used instructions on Rust Inside Other Languages

and created my first .dll library
but now I have no idea how to use that .dll in my C program
or C++ program, everywhere its said that I need a .h file

(i can work on Linux too, so (.so) library integrations tips are welcome too )

and if anyone knows, I would like to know that how to use these .dll/.so files in by other rust projects

2 Likes

Shared/Dynamic libraries are just libraries. You need to write a header file that matches the prototypes you export for use in other languages. Since the export will be C (due to the C++ ABI being a problem), you'll want to write out the prototypes as C types.

It really depends on what you wrote, and I can't remember all of the details off the top of my head, but taking the example and modifying it ever so slightly:

use std::thread;

#[no_mangle]
pub extern fn process(count: usize) {
    let handles: Vec<_> = (0..count).map(|_| {
        thread::spawn(|| {
            let mut x = 0;
            for _ in 0..5_000_000 {
                x += 1
            }
            x
        })
    }).collect();

    for h in handles {
        println!("Thread finished with count={}",
        h.join().map_err(|_| "Could not join a thread!").unwrap());
    }
}

I just spun up a new crate and added:

[lib]
name = "dlltest"
crate-type = ["dylib"]

And a quick, hacky, MSVC application to test it:

// Cross-platform bootstrap
#if defined(_WIN32) || defined(_WIN64)
#	ifdef __GNUC__
#		define API_EXPORT __attribute__ ((dllexport))
#		define API_IMPORT __attribute__ ((dllimport))
#	else
#		define API_EXPORT __declspec(dllexport)
#		define API_IMPORT __declspec(dllimport)
#	endif
#       define API_STATIC
#else
#	ifdef __GNUC__
#		define API_EXPORT __attribute__((visibility ("default")))
#		define API_IMPORT __attribute__((visibility ("default")))
#	else
#		define API_EXPORT
#		define API_IMPORT
#	endif
#   define API_STATIC
#endif

extern "C" {
	API_IMPORT void process(size_t count);
}

int main(int argc, char **argv)
{
	process(15);
	return 0;
}

The ifdef in the C++ is probably out of date, I pulled it from an old snippet to bootstrap because I didn't want to restrict to a single platform. Chances are you want attribute on everything these days, but since it's from when I used to use g++ on Linux (and almost never Clang), it's probably dated.


Edit: To be clear, the "header" content is this:

extern "C" {
	API_IMPORT void process(unsigned int count);
}

I took the prototype from Rust and made it into a C prototype. There's nothing particularly special about it, save that usize becomes unsigned int, and the return value not being specified in Rust is void. The rest of what I didn't say is basic C++/Rust programming.

4 Likes

usize is the same side as size_t in C, not unsigned int FYI

1 Like

Good to know, but the reason I auto-piloted to unsigned int is because I avoid size_t in C++ code (due to what are now mostly legacy issues). For proper binds I'd use u32 or u64 to be explicit -- I don't even like using int in C. Both isize and usize took a bit of getting used to in Rust (because size_t can't always be relied upon on certain systems I've written code on).

I'll update the above post to change it to size_t.

I know I'll sound dumb , but in all this , I really never saw you specifying path to the DLL , how does it knows where dll is and , that

Bootstrap + extern "C" {API_IMPORT... } Part can be put in saprate .h file ?

We were all new to these things once upon a time :slight_smile: I'm far, far, from an expert on these things, I've just played a bit, so I could point you in the right direction for some of it.

The shared/dynamic library (.dll or .so) is in the target/release (or target/debug) directory (if you setup the [lib] block correctly, otherwise it'll only have a rlib). It also generates a .lib file (on Windows; .a on all other platforms I believe--I use CMake for integration, so I'd just point it at the directory normally). You link the .lib on Windows, and then copy the .dll into the working directory (x64/Debug or Debug, typically).

It's more of a compiler specific question as to how you set it up (i.e. flags or project files).

The bootstrap isn't necessary, I just put it there so if you used it on another platform it would behave itself. It's just doing the shared/dynamic import/export in a neat way. If you build static libraries (which Rust allows you to build if you want) you don't have to export, it could just be (in my example):

extern "C" {
    void process(size_t count);
}

with the crate changing from:

crate-type = ["dylib"]

to:

crate-type = ["staticlib"]

The extern block tells the C++ compiler that what's in there uses C names (so it isn't mangled). You can put that in a .h, but all a .h or .hpp file is is a header, which is typically used to contain certain types of things that are better put there--forward declarations (prototypes), and in C++ templates/generics (because they won't expand automatically unless you specialise, but that's really a huge C++ concern and not one you'll face exporting from Rust!).

Before you start worrying too much about this I'd say settle on learning one language fairly well. Rust is a great language, so start there by all means. On the other hand, if you learn C++, there's nothing wrong with it either--worth noting I have a bias towards Rust because I like it, not because this is the Rust forum :slight_smile:. What you're struggling with there isn't particularly complex, it's actually fairly basic linking issues in C or C++. If you need to link the two, even at their most basic, I'd recommend going through some fairly basic C++ tutorials, or just guides for the things you need. As you seem to be using Windows, check out the MSDN guides (like this one on creating and using dynamic libraries (dlls)).

Note that while I say not particularly complex, and fairly basic, it's hard for me to really equate it. It has been a long time since I learned to write this kind of code, and I've tackled this issue dozens of times from different angles. The trick to learning to integrate two (or more) languages is knowing the languages in question fairly well. This doesn't mean you need substantial depth, just that you need to understand how they choose to link. The Rust documentation is pretty good, but it does seem to suggest you need some C/C++ knowledge (which is probably a fair assumption for most people, but obviously not everyone). Since Rust (and most other languages) rely on C-based ABIs (noting that C doesn't have a universal standard, with GNU C ABI and MSVC ABI being the two you'll run into the most on Windows), it helps to know C and/or C++, at least enough to know/understand linking.

As you seem to want to work on both sides of it, I'd ask myself why. If you're writing both, why split it? If it's a library, is there an alternative? I'm not saying don't learn both languages, I'm just suggesting that splitting your focus (while still struggling with these things) isn't going to help you progress, and it just adds more potential roadblocks into your path (all at the same time).

my basic purpose for doing this is to break my c program , certain reasons

#1 file reading file via functions like fscanf(); is lot faster than rust and lot simpler because given the structure of file directly picking up numbers as integers is possible as file is refined
#2 that dll failed in all languages "ffi" functionality i tried , namely ruby, python , C#

# here is the python code :

from ctypes import cdll
lib = cdll.LoadLibrary("C:/Users/Dell/Desktop/hack/embed.dll")

lib.test.hello()

pass

here is the python error

C:\Users\Dell\Desktop\rust\testlib>C:\Users\Dell\Desktop\hack\sam.py
Traceback (most recent call last):
  File "C:\Users\Dell\Desktop\hack\sam.py", line 12, in <module>
lib.test.hello()
  File "C:\Users\Dell\AppData\Local\Programs\Python\Python37\lib\ctypes\__init__.py", line 369, in __getattr__
func = self.__getitem__(name)
  File "C:\Users\Dell\AppData\Local\Programs\Python\Python37\lib\ctypes\__init__.py", line 374, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: function 'test' not found

here is C# code

using System;
using System.Runtime.InteropServices;

namespace hello_c_sharp
{
class Program
{
    [DllImport("C:/Users/Dell/Desktop/rust/testlib/target/release/embed.dll")]
    public static extern void Hello();
    static void Main(string[] args)
    {
       
        Hello();
        Console.ReadLine();

      //  System("pause");
    }
}
}

here is the C# error

Exception thrown: 'System.EntryPointNotFoundException' in hello_c_sharp.dll
An unhandled exception of type 'System.EntryPointNotFoundException' occurred in 
hello_c_sharp.dll
Unable to find an entry point named 'Hello' in DLL 
'C:/Users/Dell/Desktop/rust/testlib/target/release/embed.dll'.

You could probably use serde (or similar) to turn it into a structure + derive scenario, but Rust's read/write isn't actually that slow (particularly if you can use unsafe code; though don't stress about speed initially, particularly if you're in debug builds).

Under the hood the functions you use are either going to be the same as C/C++, or you can access them (if you really want them). You can write your own fscanf if you need to do so, or use an existing crate (there are quite a few).

That makes it sound like you're exporting something incorrectly (in some way). Does each function you want have the following?

#[no_mangle]
pub extern fn

This is a bit strange, because you're asking for test.hello() in Python

lib.test.hello()

and just Hello() in C#. Am I missing something here?

[DllImport("C:/Users/Dell/Desktop/rust/testlib/target/release/embed.dll")]
public static extern void Hello();

I'm honestly not that familiar with FFI in C# (I look it up each time I need to do it), so I can't say much about it. But for the Python example try removing the "test" part?

From my previous test (on Windows):

>>> import ctypes
>>> lib = ctypes.WinDLL("dlltest.dll")
>>> lib.process(5)
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
1

i did tried to remove , problem was the same

     C:\Users\Dell\Desktop\rust\testlib>C:\Users\Dell\Desktop\hack\sam.py
Traceback (most recent call last):
  File "C:\Users\Dell\Desktop\hack\sam.py", line 4, in <module>
    lib.hello()
  File "C:\Users\Dell\AppData\Local\Programs\Python\Python37\lib\ctypes\__init__.py", line 369, 
   in __getattr__
    func = self.__getitem__(name)
    File "C:\Users\Dell\AppData\Local\Programs\Python\Python37\lib\ctypes\__init__.py", line 374, 
    in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
    AttributeError: function 'hello' not found

here is my rust code (test code, so its just hello )

#[no_mangle]
pub mod Tests {
   pub fn Hello() {
    	println!("hello");
        //assert_eq!(2 + 2, 4);
    }
}

but i think you are right , i should practice both languages for some time before i try to mix them
as rust not quite the kind of programming language i am used to , so it should take a lot of time

For what it's worth (edit: for clarity, C doesn't have namespaces, and we're playing in the C ABI. In C++ we export namespaces through mangling, so it's a bit more complicated and there's a lot of what boils down to magic in there):

pub mod tests {
    #[no_mangle]
    pub extern fn hello() {
    	println!("hello");
        //assert_eq!(2 + 2, 4);
    }
}

Appears to become:

extern "C" {
    void hello();
}

So for FFI in Python you'd just use lib.hello():

>>> import ctypes
>>> lib = ctypes.WinDLL("dlltest.dll")
>>> lib.hello()
hello
-1692470976
>>>

The other issue is your naming: Test and Hello, while okay in a sense, aren't the norm in Rust. I try to stick to lowercase so it's consistent.

1 Like

NOOOOOOO!!!!

Seriously, please never ever use the dylib crate type. You always want the cdylib crate type instead. It will make your .dlls so much smaller and avoid a lot of weird issues.

1 Like

What's the difference?

dylib is a non-standard Rust-specific library format. cdylib is a C-compatible dll/so.

2 Likes

So is dylib unusable from other languages?

Yes (Edit: Misread this yesterday, it's usable, not unusuable but you shouldn't use it). Both will give you FFI, but if you want to make sure it's stable/sane/portable, use cydlib. dylib itself is fine (in a sense), but if you read the docs on Linkage it describes the difference fairly clearly.

If you only want Rust interoperation, use dylib. If you want to be safer, use cdylib.

I know I said it on the Discord, but it's worth repeating: I only used it because I stepped through the tutorial (to show the tutorial was functioning).

1 Like

Thanks mate