结构中的 StringBuilder 字段无法正确封送

发布于 2024-11-15 17:21:11 字数 8621 浏览 7 评论 0原文

我已经在 p/invoke 问题上挣扎了一段时间。不断收到“尝试读取或写入受保护的内存”。一直以来,我怀疑我的编组有点不对劲。

这是 c 签名:

long ft_show(                             /* show file-attributes        */
    const struct ft_admission *admis,     /* I: transfer admission       */
    const struct ft_shwpar    *par,       /* IO: parameter-list          */
    struct ft_fileinfo        *info,      /* O: requested information    */
    struct ft_err             *errorinfo, /* O: error information        */
    void                      *options    /* I: options                  */
    );

这是相关的结构(在 c 中)

struct ft_admission
{
    char    *remsys;
    char    *remadmis;
    char    *remaccount;
    char    *rempasswd;
};

struct ft_shwpar
{
    int                shwparvers;
    char               *fn;
    char               *mgmtpasswd;
    char               *fud;
    int                fudlen;
};

struct ft_fileinfo
{
    int             ftshowivers;
    char            fn[INFO_FN_LEN];
    enum ft_ftype   filetype;
    enum ft_charset charset;
    enum ft_rform   recordform;
    long            recsize;
    enum ft_available availability;
    int             access;
    char            account[ACC_LEN];
    long            size;
    long            maxsize;
    char            legalqual[LQ_LEN];
    char            cre_user[USER_LEN];
    long            cre_date;
    char            mod_user[USER_LEN];
    long            mod_date;
    char            rea_user[USER_LEN];
    long            rea_date;
    char            atm_user[USER_LEN];
    long            atm_date;
    long long       fsize;
    long long       fmaxsize;
};

struct ft_err
{
    long    main;
    long    detail;
    long    additional;
};

struct ft_options
{                                                                       
    int ftoptsvers;
    int ftapivers;
};

我尝试创建 c# 结构和调用,如下所示:

[DllImport("ftapi.dll", EntryPoint = "ft_showdir")]
private static extern long ft_showdir(ft_admission admis, ref ft_shwpar par, ft_fileinfo[] buf, int bufsize, ref ft_err errorinfo, ft_options options);

    public struct ft_admission
    {
        public String remsys;
        public String remadmis;
        public String remaccount;
        public String rempasswd;
    };
    public struct ft_shwpar
    {
        public int shwparvers;
        public String fn;
        public String mgmtpasswd;
        public IntPtr fud;
        public int fudlen;
    };
    [StructLayout(LayoutKind.Sequential, Size = 1464, CharSet = CharSet.Ansi), Serializable]
    public struct ft_fileinfo
    {
        public int ftshowivers;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
        public String fn;
        public ft_ftype filetype;
        public ft_charset charset;
        public ft_rform recordform;
        public long recsize;
        public ft_available availability;
        public int access;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
        public String account;
        public long size;
        public long maxsize;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
        public String legalqual;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
        public String cre_user;
        public long cre_date;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
        public String mod_user;
        public long mod_date;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
        public String rea_user;
        public long rea_date;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
        public String atm_user;
        public long atm_date;
        public long fsize;
        public long fmaxsize;
    };
    public struct ft_err
    {
        public long main;
        public long detail;
        public long additional;
    }
    public struct ft_options
    {
        public int ftoptsvers;
        public int ftapivers;
    };

我已经在另一个调用中成功使用了 ft_err 和 ft_options 结构,所以我认为这些结构已被封送正确。

我认为问题出在 ft_fileinfo 结构上。我想在那里使用 StringBuilder,但框架中存在一个错误,阻止在结构内部正确封送 StringBuilder。我已尝试过此处描述的解决方法:http://support.microsoft.com/kb/327109 但没有成功。

以下是我尝试访问的 api 文档中提供的代码片段(一些代码已被删除,因为它仅用于将信息打印到屏幕上):

/* sample3.c     File management requests                    */
/*************************************************************/
static const char Sccsid[] = "@(#)sample3.c   2.33 2009/03/25";
/*************************************************************

  Program call: sample3 <directory>

 *************************************************************/
/*   Include Files                                           */
/*************************************************************/
#include <stdio.h>       /* printf()                         */
#include <stdlib.h>      /* exit()                           */
#include <string.h>      /* strcat(), strcpy()               */
#include <ftapi.h>

/*************************************************************/
/*                Local Constants and Macros                 */
/*************************************************************/
#define BUFSIZE 200      /* number of ft_fileinfo structures */
                         /* in output buffer                 */

/*************************************************************/
/*               Local Functions Declarations                */
/*************************************************************/
static void error_print(struct ft_err *, char *);
static void printinfo(struct ft_fileinfo *info); 

int main (int argc, char *argv[])
{
    int i;                     /* Some local counter         */
    int count;                 /* Number of files            */
    char remotesys[201];       /* Address of remote systems  */
    char remoteadm[68];        /* Transfer admission         */
    char fud[STAT_FUD_LEN];    /* Further details            */
    struct ft_err errorinfo;   /* Error information          */
    struct ft_admission admis; /* Admission parameters       */
    struct ft_shwpar par;      /* Show directory parameters  */
    struct ft_options opt;     /* Options                    */
    struct ft_fileinfo buf[BUFSIZE]; /* Output buffer        */

    /* Check program arguments                               */
    if (argc != 2)
    {
        printf("Call: %s <directory>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Initialize ft_transpar structure                      */
    memset(&par, 0, sizeof(par));
    /* Set version of the parameter list                     */
    par.shwparvers  = FT_SPARV2;
    /* Set the name of the remote directory                  */
    par.fn = argv[1];
    /* Set buffer and buffer length for the further details  */
    par.fud = fud;               
    par.fudlen = sizeof(fud);      
    /* Set version of the ft_fileinfo structure in the       */
    /* output buffer. The version in the first structure     */
    /* is valid for all structtures in the output buffer.    */
    buf[0].ftshowivers = FT_SHOWIV2;

    /* Get the name/address of the remote system and the     */
    /* transfer admission                                    */
    printf("Enter name of remote system: ");
    scanf("%s", remotesys);
    printf("Input transfer admission to remote system: ");
    scanf("%s", remoteadm);
    admis.remsys     = remotesys;
    admis.remadmis   = remoteadm;
    admis.remaccount = NULL;
    admis.rempasswd  = NULL;

    /* Prepare the options structure                         */
    opt.ftoptsvers = FT_OPTSV1;
    opt.ftapivers = FT_APIV2; 

    /* Read the contents of the remote directory             */
    count = ft_showdir(&admis, &par, buf, BUFSIZE, &errorinfo,
                       &opt);
    if (count == -1) 
    {   
        /* Error                                             */
        error_print(&errorinfo, par.fud); 
    }

    /* Display result of request                             */
    printf("There are %d entries in remote directory %s\n",
           count, argv[1]);

    /* If the output range was not large enough for all      */
    /* the information, only the data in the buffer is       */
    /* displayed                                             */
    if (count > BUFSIZE)
        count = BUFSIZE;
    for (i = 0; i < count; i++)
         printinfo(&buf[i]);
    return(0);
}

有人可以帮助我在 C# 中实现相同的目的吗?甚至解释为什么必须这样,这样我们都可以从中学到一些东西:)

谢谢!

I've struggled with at p/invoke problem for some time now. Keep getting "Attempted to read or write protected memory." all the time, and I suspect my marshaling is a bit off.

Here is the c signature:

long ft_show(                             /* show file-attributes        */
    const struct ft_admission *admis,     /* I: transfer admission       */
    const struct ft_shwpar    *par,       /* IO: parameter-list          */
    struct ft_fileinfo        *info,      /* O: requested information    */
    struct ft_err             *errorinfo, /* O: error information        */
    void                      *options    /* I: options                  */
    );

and here are the related structures (in c)

struct ft_admission
{
    char    *remsys;
    char    *remadmis;
    char    *remaccount;
    char    *rempasswd;
};

struct ft_shwpar
{
    int                shwparvers;
    char               *fn;
    char               *mgmtpasswd;
    char               *fud;
    int                fudlen;
};

struct ft_fileinfo
{
    int             ftshowivers;
    char            fn[INFO_FN_LEN];
    enum ft_ftype   filetype;
    enum ft_charset charset;
    enum ft_rform   recordform;
    long            recsize;
    enum ft_available availability;
    int             access;
    char            account[ACC_LEN];
    long            size;
    long            maxsize;
    char            legalqual[LQ_LEN];
    char            cre_user[USER_LEN];
    long            cre_date;
    char            mod_user[USER_LEN];
    long            mod_date;
    char            rea_user[USER_LEN];
    long            rea_date;
    char            atm_user[USER_LEN];
    long            atm_date;
    long long       fsize;
    long long       fmaxsize;
};

struct ft_err
{
    long    main;
    long    detail;
    long    additional;
};

struct ft_options
{                                                                       
    int ftoptsvers;
    int ftapivers;
};

I've tried to create c# structs and calls like this:

[DllImport("ftapi.dll", EntryPoint = "ft_showdir")]
private static extern long ft_showdir(ft_admission admis, ref ft_shwpar par, ft_fileinfo[] buf, int bufsize, ref ft_err errorinfo, ft_options options);

    public struct ft_admission
    {
        public String remsys;
        public String remadmis;
        public String remaccount;
        public String rempasswd;
    };
    public struct ft_shwpar
    {
        public int shwparvers;
        public String fn;
        public String mgmtpasswd;
        public IntPtr fud;
        public int fudlen;
    };
    [StructLayout(LayoutKind.Sequential, Size = 1464, CharSet = CharSet.Ansi), Serializable]
    public struct ft_fileinfo
    {
        public int ftshowivers;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
        public String fn;
        public ft_ftype filetype;
        public ft_charset charset;
        public ft_rform recordform;
        public long recsize;
        public ft_available availability;
        public int access;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
        public String account;
        public long size;
        public long maxsize;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
        public String legalqual;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
        public String cre_user;
        public long cre_date;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
        public String mod_user;
        public long mod_date;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
        public String rea_user;
        public long rea_date;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 68)]
        public String atm_user;
        public long atm_date;
        public long fsize;
        public long fmaxsize;
    };
    public struct ft_err
    {
        public long main;
        public long detail;
        public long additional;
    }
    public struct ft_options
    {
        public int ftoptsvers;
        public int ftapivers;
    };

I've already used the ft_err and ft_options structs successfully in another call, so I think those are marshaled correctly.

I think the problem is the ft_fileinfo structure. I want to use StringBuilder in there, but there is a bug in the framework preventing correct marshaling of StringBuilder inside structs. I've tried the workarounds described here: http://support.microsoft.com/kb/327109
but no success.

Here's the code snippet provided in the documentation of the api I try to access (some code has been removed as it's only used for printing info to the screen):

/* sample3.c     File management requests                    */
/*************************************************************/
static const char Sccsid[] = "@(#)sample3.c   2.33 2009/03/25";
/*************************************************************

  Program call: sample3 <directory>

 *************************************************************/
/*   Include Files                                           */
/*************************************************************/
#include <stdio.h>       /* printf()                         */
#include <stdlib.h>      /* exit()                           */
#include <string.h>      /* strcat(), strcpy()               */
#include <ftapi.h>

/*************************************************************/
/*                Local Constants and Macros                 */
/*************************************************************/
#define BUFSIZE 200      /* number of ft_fileinfo structures */
                         /* in output buffer                 */

/*************************************************************/
/*               Local Functions Declarations                */
/*************************************************************/
static void error_print(struct ft_err *, char *);
static void printinfo(struct ft_fileinfo *info); 

int main (int argc, char *argv[])
{
    int i;                     /* Some local counter         */
    int count;                 /* Number of files            */
    char remotesys[201];       /* Address of remote systems  */
    char remoteadm[68];        /* Transfer admission         */
    char fud[STAT_FUD_LEN];    /* Further details            */
    struct ft_err errorinfo;   /* Error information          */
    struct ft_admission admis; /* Admission parameters       */
    struct ft_shwpar par;      /* Show directory parameters  */
    struct ft_options opt;     /* Options                    */
    struct ft_fileinfo buf[BUFSIZE]; /* Output buffer        */

    /* Check program arguments                               */
    if (argc != 2)
    {
        printf("Call: %s <directory>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Initialize ft_transpar structure                      */
    memset(&par, 0, sizeof(par));
    /* Set version of the parameter list                     */
    par.shwparvers  = FT_SPARV2;
    /* Set the name of the remote directory                  */
    par.fn = argv[1];
    /* Set buffer and buffer length for the further details  */
    par.fud = fud;               
    par.fudlen = sizeof(fud);      
    /* Set version of the ft_fileinfo structure in the       */
    /* output buffer. The version in the first structure     */
    /* is valid for all structtures in the output buffer.    */
    buf[0].ftshowivers = FT_SHOWIV2;

    /* Get the name/address of the remote system and the     */
    /* transfer admission                                    */
    printf("Enter name of remote system: ");
    scanf("%s", remotesys);
    printf("Input transfer admission to remote system: ");
    scanf("%s", remoteadm);
    admis.remsys     = remotesys;
    admis.remadmis   = remoteadm;
    admis.remaccount = NULL;
    admis.rempasswd  = NULL;

    /* Prepare the options structure                         */
    opt.ftoptsvers = FT_OPTSV1;
    opt.ftapivers = FT_APIV2; 

    /* Read the contents of the remote directory             */
    count = ft_showdir(&admis, &par, buf, BUFSIZE, &errorinfo,
                       &opt);
    if (count == -1) 
    {   
        /* Error                                             */
        error_print(&errorinfo, par.fud); 
    }

    /* Display result of request                             */
    printf("There are %d entries in remote directory %s\n",
           count, argv[1]);

    /* If the output range was not large enough for all      */
    /* the information, only the data in the buffer is       */
    /* displayed                                             */
    if (count > BUFSIZE)
        count = BUFSIZE;
    for (i = 0; i < count; i++)
         printinfo(&buf[i]);
    return(0);
}

Can someone help me achieve the same in C#? And even explain why it have to be that way, so we all can learn something from this :)

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

北恋 2024-11-22 17:21:11

我看到的第一个问题是您在 C# 结构声明中使用了 long 类型。将其替换为 int - C# long 具有 64 位长度,Win32 C 中的 long 与 int - 32 位长度相同。

关于 ft_fileinfo *info 参数:对于默认编组器无法处理的每个重要参数,请使用低级 Marshal 类函数。函数需要 info 来指向大小为 sizeof(fileinfo) * bufsize 的非托管内存块。将此参数定义为 IntPtr:

[DllImport("ftapi.dll", EntryPoint = "ft_showdir")]
private static extern long ft_showdir(ft_admission admis, ref ft_shwpar par, IntPtr buf, int bufsize, ref ft_err errorinfo, ft_options options);

分配非托管内存块:

IntPtr f = Marshal.AllocHGlobal(Marshal.Sizeof(typeof(ft_fileinfo)) * bufsize);

将此参数传递给非托管函数。函数返回后,使用 Marshal.PtrToStructure 方法读取 fileinfo 结构 bufsize 次,将 IntPtr 递增 SizeOf(ft_fileinfo)。不要忘记使用 Marshal.FreeHGlobal 释放非托管内存块。

您可以看到,Marshal 类允许在 C 语言级别编写互操作代码。

The first problem I see that you use long type in C# structure declarations. Replace it with int - C# long has 64 bit length, and long in Win32 C has the same size as int - 32 bit.

Regarding ft_fileinfo *info parameter: for every non-trivial parameter which cannot be handled by default marshaller, use low level Marshal class functions. Function requires info to point to unmanaged memory block with size sizeof(fileinfo) * bufsize. Define this parameter as IntPtr:

[DllImport("ftapi.dll", EntryPoint = "ft_showdir")]
private static extern long ft_showdir(ft_admission admis, ref ft_shwpar par, IntPtr buf, int bufsize, ref ft_err errorinfo, ft_options options);

Allocate unmanaged memory block:

IntPtr f = Marshal.AllocHGlobal(Marshal.Sizeof(typeof(ft_fileinfo)) * bufsize);

Pass this parameter to unmanaged function. After function returns, read fileinfo structures using Marshal.PtrToStructure Method bufsize times, incrementing IntPtr by SizeOf(ft_fileinfo). Don't forget to release unmanaged memory block using Marshal.FreeHGlobal.

You can see, Marshal class allows to write interoperability code on C language level.

画中仙 2024-11-22 17:21:11

您的 DllImport 属性中的 ft_fileinfo 之前似乎缺少 ref。我通过以下删减重现了您的问题。在 C++ 中定义入口点:

    // OneEntryPoint.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include <stdio.h>
extern "C"
{
    struct AStruct
    {
        char     fn[11];
    };

    __declspec(dllexport) void AnEntry(AStruct* someStruct) 
    {
        printf("A character %c", someStruct->fn[255]); 
    }
}

在 C# 中重现调用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication2
{

    public struct AStruct
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
        public string fn; 
    }


    class Program
    {
        [DllImport("OneEntryPoint", CallingConvention = CallingConvention.Cdecl)]
         static extern void AnEntry(AStruct somestruct); 


        static void Main(string[] args)
        {
            try
            {
                AStruct fred;
                fred.fn = "hello world";
                Program.AnEntry(fred);
                Console.WriteLine("Done");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }

运行此命令,您将收到 AccessViolationException(尝试读\写受保护的内存)。将引用放入,结构已正确编组并且没有错误。

It looks like you are missing a ref before ft_fileinfo in your DllImport attribute. I reproduced your issue with the following cutdown. In C++ to define the entry point:

    // OneEntryPoint.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include <stdio.h>
extern "C"
{
    struct AStruct
    {
        char     fn[11];
    };

    __declspec(dllexport) void AnEntry(AStruct* someStruct) 
    {
        printf("A character %c", someStruct->fn[255]); 
    }
}

and in C# to reproduce the call:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication2
{

    public struct AStruct
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
        public string fn; 
    }


    class Program
    {
        [DllImport("OneEntryPoint", CallingConvention = CallingConvention.Cdecl)]
         static extern void AnEntry(AStruct somestruct); 


        static void Main(string[] args)
        {
            try
            {
                AStruct fred;
                fred.fn = "hello world";
                Program.AnEntry(fred);
                Console.WriteLine("Done");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }

Run this and you get an AccessViolationException (attempt to read\write protected memory). Put the ref in and the struct is correctly marshalled and there are no errors.

风蛊 2024-11-22 17:21:11

好吧,我明白了。

当查看函数的 c 声明时,我们可以看到所有参数都是通过引用或通过指针发送的。所以我只是更改了 pinvoke 声明,以便所有参数都在 ref 处。

[DllImport("ftapi.dll", EntryPoint = "ft_showdir", CallingConvention = CallingConvention.Cdecl)]
private static extern Int32 ft_showdir(ref ft_admission admis, ref ft_shwpar par, ref ft_fileinfo[] buf, int bufsize, ref ft_err errorinfo, ref ft_options options);

Ok, I figured it out.

When looking at the c declaration of the function we can see that all of the parameters are sent in by reference or via pointers. So I just altered the pinvoke declaration so that all parameters where ref.

[DllImport("ftapi.dll", EntryPoint = "ft_showdir", CallingConvention = CallingConvention.Cdecl)]
private static extern Int32 ft_showdir(ref ft_admission admis, ref ft_shwpar par, ref ft_fileinfo[] buf, int bufsize, ref ft_err errorinfo, ref ft_options options);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文