How to share memory/data between functions in a Win32 DLL?

Hi,

I am writing a Win32 DLL (crate-type = ["cdylib"]), target=i686-pc-windows-msvc) as a plugin for an external application.

The DLL has 3 functions that are called from the application:

pub unsafe extern "stdcall" fn PluginStart(aOwner: uintptr_t) { ... }
pub unsafe extern "stdcall" fn PluginFinalize() { ... }
pub unsafe extern "stdcall" fn AccessVariable(
    variableIndex: u8,
    value: *const c_float,
    writeValue: *const bool,
) { ... }

PluginStart is called when the application starts and loads all plugins, PluginFinalize is called when the application ends.

AccessVariable is called around 20-100 times per second per variable (around 20-30 different variables in total) and gives some internal variables/values to the plugin.

AccessVariable should be fast/small and must be non-blocking, because it impacts speed and stability of the application.

The structure I have planned is that AccessVariable copies the received values ​​in an array, does nothing else and returns immediately. And at the beginning PluginStart starts a thread that carries out the actual processing of the data (checking whether values ​​have changed, conversions, forwarding data to a serial port, etc.)

However, the data must somehow get from AccessVariable to the thread started by PluginStart. In C++ (with other plugins that are not mine) a global array of floats is simply created, which is then accessed. Yes, I know that this is bad and why it is bad.

How can and should I do this in Rust? I have searched, including in this forum, and have not found anything suitable. The solutions I have found are based on there being some kind of "main" from which the threads that communicate are started/spawned. But there is no "main" in this DLL.

Any kind of tips and help and links to solutions are very welcome. Thanks a lot.

It's a shame that the plugin API doesn't support user-provided data pointer. C APIs usually have something like void* user_data which could be used in Rust to hold data without using globals.

But if you have to use globals, then you will need to make them thread safe. If the data is read-only, then once_cell has ways of lazy-initializing globals. If the data is mutable, you will have to use Mutex or Atomic* types.

// you can skip Option if the Data contains simple types that 
// can be initialized without heap allocations
static SHARED_PLUGIN_DATA: Mutex<Option<Data>> = Mutex::new(None); 

// or
static SHARED_PLUGIN_NUM: AtomicU32 = AtomicU32::new(0);
1 Like

Thanks, I appreciate your help.

In the DLL, only AccessVariable can write to it, all other accesses are read-only. Apart from the initial initialization, of course. And of course I can't guarantee that only one AccessVariable function will run at a time.

I'll take a closer look at once_cell, Mutex and Atomic, I don't have any experience with any of them yet.

At first glance, an array of AtomicU32 (I don't need floats or signed integers) seems like a very promising solution.

Thanks.

Thank you very much. The AtomicU32 has solved my problem. It works very well.

Is there a way to write the following more briefly? With more array elements it becomes quite long.


const SHARED_ARRAY_SIZE: usize = 10;

static SHARED_ARRAY: [AtomicU32; SHARED_ARRAY_SIZE] = [
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),    
];
const INITIAL_VALUE: AtomicU32 = AtomicU32::new(0);

static ARRAY: [AtomicU32; SIZE] = [INITIAL_VALUE; SIZE];
2 Likes

Keeping it simple is nearly always the right option, but if you wanted to tackle the start a thread, no blocking option, you would be using a channel, and storing the Sender in the global.

You could use an UnsafeCell if you really want to avoid locking to access it, though this is, as the name implies, unsafe. Fortunately Senders are both Sync (you can use them across threads) and send takes a shared self reference (meaning you don't need to lock them), so so long as you definitely initialize before accessing it you should be all good.

The main trouble is you need to be sure you can keep up with the input or you're just creating a memory leak as the buffer fills up further and further, and if you can do so trivially it normally means you don't need to do anything fancy.

Higher performance but fiddler is a ring buffer, eg ringbuf β€” Rust concurrency library // Lib.rs

Another option to consider is thread locals, depending on what you're attempting they can be much faster to access than atomics or locking, but they are, of course, local to each thread and can't be aggregated.

Past that, you're getting into the really fancy stuff: lock free data structures and the like. You probably don't need this.

2 Likes

In your example, wouldn't all array elements be initialized with the same AtomicU32 (same reference)? Because all elements are initialized with the same INITIAL_VALUE? Or are things happening behind the scenes that I don't understand and in reality an AtomicU32::new(0) is executed for each array element? Thanks.

The initial value is a const (ergo: computed at compile time), and hence is Copy. So the array initialization can proceed as it would with any Copy value. see @SkiFire13 reply below

There are no implicit references in Rust. You have to see a & or a pointer wrapping type (such as but not limited to Rc and Arc) to have references.

A const represents a compile time value, not a variable. It doesn't have an identity of its own. So each element of the array initially gets initialized with the same value, but ultimately they will be different.

consts are not Copy, they just satisfy the requirement to be used in an array initializer expression (be either a path to a const item or an expression of a Copy type; in future versions there will be a third possibility which is a const {} block)

2 Likes

My original problem is already solved. Just to understand this, let me describe it with my own words: A const in Rust is (like a macro in several languages) replaced/evaluated/computed at compile time and therefore the statement example

const INITIAL_VALUE: AtomicU32 = AtomicU32::new(0);
static ARRAY: [AtomicU32; 20] = [INITIAL_VALUE; 20];

would mean that INITIAL_VALUE is computed during array initialization 20 times, so AtomicU32::new(0) is evaluated/computed 20 times and therefore 20 AtomicU32 would be created. Did I understand that correctly? Please say "Yes" :wink:

Yes. You can think of it as a macro for values (i.e. each occurence of the const is replaced by the value it evaluates to).

You're right that 20 AtomicU32s will be created, however it's not really observable the fact that AtomicU32::new(0) was computed/evaluated 20 times or not (in practice this happens only once because the computation is pure and so the result can be reused)

1 Like