See the question and my original answer on StackOverflow

The binary equivalent to size_t is IntPtr (or UIntPtr). But for parameters, you can just use int or uint without any additional attribute.

So, if you have this in C/C++:

int InitOptions(size_t param1, size_t param2);

then you can declare it like this in C# and it will work for x86 and x64 (well, you won't get any bit value above 32 of course, the hi-uint is lost):

[DllImport("my.dll")]
static extern int InitOptions(int param1, int param2); // or uint

For x86 it works because, well, it's just supposed to.

For x64, it works magically because arguments are always 64-bit, and luckily, the extra hi-bits are zeroed by errrhh... some components of the system (the CLR? C/C++ compiler? I'm unsure).

For struct fields this a complete different story, the simplest (to me) seems to use IntPtr and add some helpers to ease programming.

However, I've added some extra sample code if you really want to add some sugar for the developers using your structs. What's important is this code could (should) be generated from the C/C++ definitions.

public static int InitOptions(ref Options options)
{
    if (IntPtr.Size == 4)
        return InitOptions32(ref options);

    Options64 o64 = options;
    var i = InitOptions64(ref o64);
    options = o64;
    return i;
}

[DllImport("my64.dll", EntryPoint = "InitOptions")]
private static extern int InitOptions64(ref Options64 options);

[DllImport("my32.dll", EntryPoint = "InitOptions")]
private static extern int InitOptions32(ref Options options);

[StructLayout(LayoutKind.Sequential)]
public struct Options // could be class instead (remove ref)
{
    public Flags flags;
    public uint a;
    public uint b;
    public uint c;

    public static implicit operator Options64(Options value) => new Options64 { flags = value.flags, a = value.a, b = value.b, c = value.c };
}

[StructLayout(LayoutKind.Sequential)]
public struct Options64 // could be class instead (remove ref)
{
    public Flags flags;
    public ulong a;
    public ulong b;
    public ulong c;

    public static implicit operator Options(Options64 value) => new Options { flags = value.flags, a = (uint)value.a, b = (uint)value.b, c = (uint)value.c };
}

Note that if you uses classes instead of struct for Options and Options64, you can remove all the ref argument directions and avoid the painful copy from structs (operator overloading doesn't work well with ref). But this has other implications, so it's up to you.

Here is another discussion on the same subject: C# conditional compilation based on 32-bit/64-bit executable target

Basically, what you could also do is use conditional compilation constants for x86 and x64 targets and have your code vary using that.