访问违规读取位置传递指针以在c中写入的DLL中起作用

access violation reading location when passing pointers to function in DLL written in c

本文关键字:起作用 DLL 读取 位置 指针 访问      更新时间:2023-10-16

我正在尝试调用一个在方言之间转换SQL查询的函数。我正在使用开源项目的DLL在C#大学项目中使用这些功能进行转换。我面临的问题是我正在遇到访问违规的阅读地点。

我已经在堆栈溢出上阅读了一些帖子,这表明某处可能有一个坏的指针,但我找不到哪里。我的指针没有损坏

转换的功能是:

       int ConvertSql(void *parser, const char *input, int64_t size, const char 
       **output, int64_t *out_size, int64_t *lines)
        {
            if(parser == NULL)
                return -1;
            SqlParser *sql_parser = (SqlParser*)parser;
            // Run conversion
            sql_parser->Convert(input, size, output, out_size, lines);
            return 0;
        }

我在C#

中调用该功能
                        char *parentInput;
                        fixed(char *input = &inputStr.ToCharArray()[0])
                        {
                            parentInput = input;
                        }
                        char** output = null;
                        Int64 out_size = 0;
                        Int64 lines = 0;
                        Int64 size = inputStr.Length;
                        Console.WriteLine(new IntPtr(&out_size)+"  "+ new IntPtr(&lines)+"  "+new IntPtr(&parserObj)+"   "+new IntPtr(output));
                        int result = ConvertSql(&parserObj, intputStr, size, output, &out_size, &lines);

我从此代码中获取我的解析器对象,该对象无需错误:

IntPtr parserObj = CreateParserObject();

函数的dllimport正在使用此代码:

[DllImport(dllName: "PATHTODLLFOLDER\sqlparser.dll", EntryPoint = "CreateParserObject", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CreateParserObject();
[DllImport(dllName: "PATHTODLLFOLDER\sqlparser.dll", EntryPoint = "ConvertSql", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern int ConvertSql(void *parser, String input, Int64 size, char **output, Int64 *out_size, Int64 *lines);

在.net中,通过p/indoke调用一个不受管理的方法(这是您称为 extern方法时发生的情况(涉及各种类型的转换参数,这被称为" marshalling"并且由称为"卫生机"的运行时自动完成。

一般而言,这是元帅指针的一个可怕想法。相反,通过更改P/调用方法的签名来使用CLR MASHALLER将某些类型转换为指针的能力:

// split on multiple lines for readability
[DllImport("PATHTODLLFOLDER\sqlparser.dll", EntryPoint = "ConvertSql", CallingConvention = CallingConvention.Cdecl)]
public static extern int ConvertSql(
    IntPtr parser, 
    [MarshalAs(UnmanagedType.LPStr)] string input,
    long size, 
    out IntPtr output, // see explanation below
    out long out_size, 
    out long lines);

关于上述内容的几件事。首先,我自由使用C#类型别名(字符串,长(,因为这是更惯用的C#,但它不会改变行为。另外,由于不再有指针,因此不需要unsafe

首先,我将parser宣布为IntPtr,因为如果需要,它们会自动转换为void*,这就是CreateParserObject()已经返回的。

其次,可以将参数转换为指示器转换为非初始化的对象,因此,通过将out_sizelines标记为out,您可以解决其他问题。

input是一个字符串,它具有.net中的特定格式。由于您的功能需要const char*,因此您需要告诉卫生员如何转换字符。这就是MarshalAs属性所在的地方:默认转换对您不起作用的时候。UnmanagedType.LPStr在这种情况下表示char*,因此字符串会转换。运行时将为您管理内存。

但是在这里,我们在道路上遇到了一个大障碍:输出。编造事物时,总会有关于终生的疑问,或者是特别是:谁发布了记忆?outputchar**,这一事实意味着解析器分配了一个内存块,然后将其返回,这意味着呼叫者必须释放它。从一开始,由于呼叫者不知道如何分配内存,因此C 设计不良。是malloc吗?new[]?平台特定的API(例如LocalAlloc(?它是指向静态内存块的指针吗?通常,这些API随附文档,确切地告诉您完成指针后如何处理它。良好的C API返回智能指针,或要求呼叫者通过以前分配的内存块,然后可以使用。

但是,这是您正在玩的事情,因此这是使其正常工作的方法。首先,您会认为您可以将output声明为[MarshalAs(UnmanagedType.LPStr)] out string:MARSHALLER会将字符复制到托管字符串中并返回...但是随后本机字符串(在C 侧(内存将泄漏,因为运行时没有运行时知道字符串是如何分配的,因此更喜欢对此不做任何事情。另外,假设字符串是无效的,可能并非总是如此。

因此,另一个选择是将output声明为out IntPtr。然后,您可以使用Marshal.PtrToStringAnsi将指针转换为字符串,然后将其释放...但是您需要首先知道它是如何分配的。

将它们放在一起:

var parserObj = CreateParserObject();
var output = IntPtr.Zero;
try
{
    long lines;
    long out_size;
    int result = ConvertSql(parserObj, inputStr, inputStr.Length, out output, out out_size, out lines);
    var outputStr = Marshal.PtrToStringAnsi(output, (int)out_size);
    // do what you want with outputStr here
}
finally
{
    if (output != IntPtr.Zero)
    {
        // release output here
    }
}

哦,还有一个最后的想法:CreateParserObject()返回的任何内容都可能必须在某一时刻释放。您可能需要另一个功能,可能是:

[DllImport(/* ... */)]
public static extern void DestroyParserObject(IntPtr parserObject);

它甚至可能已经存在于您的DLL中。

祝你好运!