tolsen64 tolsen64 - 6 months ago 21
Vb.net Question

How to marshal a structure in .NET to be used by native code?

I'm trying to write a DLL in .NET that can be called from a C++ executable. The executable expects a specific DLL to exist in its folder and expects a specific function name to be exported for it to consume. I'm using info from this Unmanaged Exports page to do it.

I have the following struct in C++ which I have to accept when the .NET function is called:

#pragma pack(4)

typedef struct sFMSelectorData
{
// sizeof(sFMSelectorData)
int nStructSize;

// game version string as returned by AppName() (ie. in the form "Thief 2 Final 1.19")
const char *sGameVersion;

// supplied initial FM root path (the FM Selector may change this)
char *sRootPath;
int nMaxRootLen;

// buffer to copy the selected FM name
char *sName;
int nMaxNameLen;

// set to non-zero when selector is invoked after game exit (if requested during game start)
int bExitedGame;
// FM selector should set this to non-zero if it wants to be invoked after game exits (only done for FMs)
int bRunAfterGame;

// optional list of paths to exclude from mod_path/uber_mod_path in + separated format and like the config
// vars, or if "*" all mod paths are excluded (leave buffer empty for no excludes)
// the specified exclude paths work as if they had a "*\" wildcard prefix
char *sModExcludePaths;
int nMaxModExcludeLen;
} sFMSelectorData;


But I haven't the slightest clue how to marshal everything. Here's my structure currently. You can see I've been trying to experiment. If I remove all the marshalling attributes, I get the MessageBox when the C++ code calls the function (below), but the data in the variables of the structure are not what's expected. When I attempt to add marshalling attributes like this example, the C++ code crashes and terminates. I was trying to match the
#pragma pack(4)
from the C++ structure layout, but not sure how to fiddle with the strings to make them compatible with what I guess are pointers in the C++ struct. Also, I'm guessing that the
<FieldOffset(0)>
attributes refers to the byte index of that variable within the struct. I had to stop there and decided to post this question.

<StructLayout(LayoutKind.Sequential, Pack:=4)>
Public Structure sFMSelectorData
' sizeof(sFMSelectorData)
Dim nStructSize As Integer

' game version string as returned by AppName() (ie. in the form "Thief 2 Final 1.19")
<MarshalAs(UnmanagedType.LPStr)>
Dim sGameVersion As String

' supplied initial FM root path (the FM Selector may change this)
<MarshalAs(UnmanagedType.LPStr)>
Dim sRootPath As String
Dim nMaxRootLen As Integer

' buffer to copy the selected FM name
<MarshalAs(UnmanagedType.LPStr)>
Dim sName As String
Dim nMaxNameLen As Integer

' set to non-zero when selector Is invoked after game exit (if requested during game start)
Dim bExitedGame As Integer
' FM selector should set this to non-zero if it wants to be invoked after game exits (only done for FMs)
Dim bRunAfterGame As Integer

' optional list of paths to exclude from mod_path/uber_mod_path in + separated format And Like the config
' vars, Or if "*" all Mod paths are excluded (leave buffer empty for no excludes)
' the specified exclude paths work as if they had a "*\" wildcard prefix
<MarshalAs(UnmanagedType.LPStr)>
Dim sModExcludePaths As String
Dim nMaxModExcludeLen As Integer
End Structure


So the C++ code is calling this .NET function:

<DllExport(CallingConvention:=CallingConvention.Cdecl, ExportName:="SelectFM")>
Public Function SelectFM(<MarshalAs(UnmanagedType.Struct)> ByRef data As sFMSelectorData) As Int32

Select Case MsgBox("Start the game?", MsgBoxStyle.Question Or MsgBoxStyle.YesNo, data.nStructSize)
Case MsgBoxResult.Yes : Return eFMSelReturn.kSelFMRet_Cancel
Case MsgBoxResult.No : Return eFMSelReturn.kSelFMRet_ExitGame
End Select

Return eFMSelReturn.kSelFMRet_Cancel

End Function


It does what I want. When I click Yes in the
MessageBox
, the game starts. When I click No, the game closes. But I need to use the data that's supposed to be populated into the structure. I'm not there yet.

Here's what the documentation says


An FM selector is a separate library (DLL) containing a utility, usually a UI based application, that lists the available FMs and lets the user pick which one to run. A selector could range from a simple list box with the FM names to a full blown manager with extended info, last played timestamps, sorting/filtering etc.

The default name for the selector is "FMSEL.DLL", but can be configured with the "fm_selector" cam_mod.ini var.

Exports



The DLL only needs to have a single symbol exported "SelectFM", which
is a function in the form of:

int __cdecl SelectFM(sFMSelectorData *data);


The following return values are defined:

0 = 'data->sName'
is expected to contain the selected FM name, if
string is empty it means no FM 1 = cancel and exit game

Any other value is interpreted as cancel-and-continue, the game will
start using the
cam_mod.ini
based active FM if defined, otherwise it
will run without any FM.

Data types



#pragma pack(4)

typedef struct sFMSelectorData { // sizeof(sFMSelectorData) int structSize;

// game version string as returned by AppName() (ie. in the form "Thief 2 Final 1.19")
const char *sGameVersion;

// supplied initial FM root path (the FM selector may change this)
char *sRootPath; int nMaxRootLen;

// buffer to copy the selected FM name
char *sName; int nMaxNameLen;

// set to non-zero when selector is invoked after game exit (if requested during game start)
int bExitedGame;

// FM selector should set this to non-zero if it wants to be invoked after game exits (only done for FMs)
int bRunAfterGame;

// optional list of paths to exclude from mod_path/uber_mod_path in + separated format and like the config
// vars, or if "*" all mod paths are excluded (leave buffer empty for no excludes)
// the specified exclude paths work as if they had a "*\" wildcard prefix
char *sModExcludePaths; int nMaxModExcludeLen;

// language setting for FM (set by the FM selector when an FM is selected), may be empty if FM has no
// language specific resources
// when 'bForceLanguage' is 0 this is used to ensure an FM runs correctly even if it doesn't support
// the game's current language setting (set by the "language" config var)
// when 'bForceLanguage' is 1 this is used to force a language (that must be supported by the FM) other
// than the game's current language
char *sLanguage; int nLanguageLen; int bForceLanguage;
} sFMSelectorData;

#pragma pack()

typedef enum eFMSelReturn {
kSelFMRet_OK = 0, // run selected FM 'data->sName' (0-len string to run without an FM)
kSelFMRet_Cancel = -1, // cancel FM selection and start game as-is (no FM or if defined in cam_mod.ini use that)
kSelFMRet_ExitGame = 1 // abort and quit game
} eFMSelReturn;

typedef int (__cdecl *FMSelectorFunc)(sFMSelectorData*);



Hoping some bilingual C++/.NET guru can help me out.

Answer

With DllImport this structure would be

<StructLayout(LayoutKind.Sequential, Pack:=4)>
Public Structure sFMSelectorData
    Public nStructSize As Integer

    <MarshalAs(UnmanagedType.LPStr)>
    Public sGameVersion As String

    ' supplied initial FM root path (the FM Selector may change this)
    <MarshalAs(UnmanagedType.LPStr)>
    Public sRootPath As String
    Public nMaxRootLen As Integer

    ' buffer to copy the selected FM name
    <MarshalAs(UnmanagedType.LPStr)>
    Public sName As String
    Public nMaxNameLen As Integer

    ' set to non-zero when selector Is invoked after game exit (if requested during game start)
    Public bExitedGame As Integer
    ' FM selector should set this to non-zero if it wants to be invoked after game exits (only done for FMs)
    Public bRunAfterGame As Integer

    ' optional list of paths to exclude from mod_path/uber_mod_path in + separated format And Like the config
    ' vars, Or if "*" all Mod paths are excluded (leave buffer empty for no excludes)
    ' the specified exclude paths work as if they had a "*\" wildcard prefix
    <MarshalAs(UnmanagedType.LPStr)>
    Public sModExcludePaths As String
    Public nMaxModExcludeLen As Integer
End Structure

Whether that works depends on exactly what that DllExport does.

Comments