I'm in the process of rewriting an overengineered and unmaintainable chunk of my company's library code that interfaces between C# and C++.  I've started looking into P/Invoke, but it seems like there's not much in the way of accessible help.
We're passing a struct that contains various parameters and settings down to unmanaged codes, so we're defining identical structs.  We don't need to change any of those parameters on the C++ side, but we do need to access them after the P/Invoked function has returned.
My questions are:
  What is the best way to pass strings?  Some are short (device id's which can be set by us), and some are file paths (which may contain Asian characters)
  Should I pass an IntPtr to the C# struct or should I just let the Marshaller take care of it by putting the struct type in the function signature?
  Should I be worried about any non-pointer datatypes like bools or enums (in other, related structs)?  We have the treat warnings as errors flag set in C++ so we can't use the Microsoft extension for enums to force a datatype.
  Is P/Invoke actually the way to go?  There was some Microsoft documentation about Implicit P/Invoke that said it was more type-safe and performant.
For reference, here is one of the pairs of structs I've written so far:
C++ 
/**
    Struct used for marshalling Scan parameters from managed to unmanaged code.
*/
struct ScanParameters
{
    LPSTR deviceID;
    LPSTR spdClock;
    LPSTR spdStartTrigger;
    double spinRpm;
    double startRadius;
    double endRadius;
    double trackSpacing;
    UINT64 numTracks;
    UINT32 nominalSampleCount;
    double gainLimit;
    double sampleRate;
    double scanHeight;
    LPWSTR qmoPath; //includes filename
    LPWSTR qzpPath; //includes filename
};
C#
/// <summary>
/// Struct used for marshalling scan parameters between managed and unmanaged code.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ScanParameters
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string deviceID;
    [MarshalAs(UnmanagedType.LPStr)]
    public string spdClock;
    [MarshalAs(UnmanagedType.LPStr)]
    public string spdStartTrigger;
    public Double spinRpm;
    public Double startRadius;
    public Double endRadius;
    public Double trackSpacing;
    public UInt64 numTracks;
    public UInt32 nominalSampleCount;
    public Double gainLimit;
    public Double sampleRate;
    public Double scanHeight;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string qmoPath;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string qzpPath;
}