Windows does not allow an x86 process to load an x64 DLL and vice versa. This is an inherent limit of Windows.
I'm assuming you're talking about NET Framework here. If you're using NET 6+ then things are little different.
You're using the term "Win32 assembly" here but is this actually a .NET assembly that was not created using the AnyCPU platform? Outside executables that should be rare unless the assembly loads a native binary. If you are working with a .NET assembly that was compiled for x86/x64 then you'll have to dynamically load the assembly at runtime. Unless you go all in on reflection then you'll need to have a set of wrapper types (likely interfaces) that the dynamically loaded types are callable through. For example supposed the assembly has an Engine class, it would need to implement an interface that resides in a third assembly that you can load that is not platform-specific. This is the common assembly. Your app would load this assembly normally and use only the types defined in it. At runtime you would need to use AppDomain.Load or LoadFile
to load the assembly from the appropriate subdirectory that you have the platform-specific binary stored in. Once the assembly is loaded you'll need to create instances of the types using this assembly. A set of wrapper types would likely make the most sense here.
If you are instead referring to a standard Win32 DLL created in something like C or C++ then you're limited to calling functions but it is probably going to be easier. You'll use DllImport to mark the functions that you need to load. Since this is a deferred call, when your application starts identify the platform you need and copy the appropriate binary into the search path so your app will find it when it is loaded. Note that if you are using NET 6+ then you can use a custom DllImportResolver to load the native DLL outside the normal search path.