简体   繁体   English

如何在.NET中编组结构以供本机代码使用?

[英]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. 我正在尝试在.NET中编写一个可以从C ++可执行文件调用的DLL。 The executable expects a specific DLL to exist in its folder and expects a specific function name to be exported for it to consume. 该可执行文件期望在其文件夹中存在特定的DLL,并期望导出特定的函数名称以供其使用。 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: 我在C ++中具有以下结构,在调用.NET函数时必须接受以下结构:

#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. 如果删除所有编组属性,则当C ++代码调用该函数时(以下),我将收到MessageBox,但结构变量中的数据不是预期的。 When I attempt to add marshalling attributes like this example, the C++ code crashes and terminates. 当我尝试像本示例一样添加编组属性时,C ++代码崩溃并终止。 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. 我试图从C ++结构布局中匹配#pragma pack(4) ,但不确定如何摆弄字符串以使其与C ++结构中的指针兼容。 Also, I'm guessing that the <FieldOffset(0)> attributes refers to the byte index of that variable within the struct. 另外,我猜想<FieldOffset(0)>属性引用了该变量在结构中的字节索引。 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: 因此,C ++代码正在调用此.NET函数:

<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. 当我在MessageBox单击是时,游戏开始。 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. FM选择器是一个单独的库(DLL),其中包含一个实用程序,通常是一个基于UI的应用程序,该实用程序列出了可用的FM,并让用户选择要运行的FM。 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. 选择器的范围可以从带有FM名称的简单列表框到具有扩展信息,最近播放的时间戳,排序/过滤等功能的功能齐全的管理器。

The default name for the selector is "FMSEL.DLL", but can be configured with the "fm_selector" cam_mod.ini var. 选择器的默认名称是“ FMSEL.DLL”,但可以使用“ 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: DLL仅需要导出“ SelectFM”单个符号,该符号的形式为:

 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 0 = 'data->sName'应该包含选定的FM名称,如果字符串为空,则表示没有FM 1 =取消并退出游戏

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. 任何其他值都将解释为“取消并继续”,游戏将使用基于cam_mod.ini的活动FM(如果已定义)开始运行,否则它将在没有任何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. 希望一些双语的C ++ /。NET专家可以帮助我。

With DllImport this structure would be 使用DllImport此结构将是

<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. 是否可行取决于DllExport的确切功能。

For .Net applications, you can use C++/CLI and you may find more information on MSDN . 对于.Net应用程序,可以使用C ++ / CLI,并且可以在MSDN上找到更多信息。 I have used C++/CLI in the past with great success for calling C++ objects in C#; 我过去曾经使用C ++ / CLI在C#中调用C ++对象取得了巨大的成功。 although, later we used SWIG for this purpose, as we needed for Java, Python, and R too. 但是,稍后我们将SWIG用于此目的,因为我们也需要Java,Python和R。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM