Reputation: 999
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 gameAny 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.
Upvotes: 0
Views: 663
Reputation: 78134
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.
Upvotes: 1