使用MethodInfo.Invoke在Win32 DLL和c#之间传递LPSTR

本文关键字:之间 LPSTR DLL MethodInfo Invoke Win32 使用 | 更新日期: 2023-09-27 17:51:24

我正在研究一个需要能够在Win32 dll中调用函数的项目。但是,DLL的名称、函数以及所有参数和返回类型的数据类型在编译时都是未知的,因此不能使用DLLImport。基本上,这个例程可以调用任何DLL中的任何函数并传入任何参数。经过大量的搜索,我已经能够成功地将一些可以做到这一点的代码组合在一起,包括向函数传递数字和字符串,甚至通过引用传递数字参数。然而,我在试图从DLL传递字符串时遇到了瓶颈。

为了简化测试,我使用Visual Studio 6编译了一个简单的Win32 DLL,名为PassCharPtr.dll,其中包含如下一个函数:

PassCharPtr.def文件:

   EXPORTS
       TestPassCharPtr

PassCharPtr.h文件:

#include <windows.h>
extern "C" int __stdcall TestPassCharPtr(LPSTR, LONG);

PassCharPtr.cpp文件:

#include "PassCharPtr.h"
int WINAPI DllEntryPoint(HINSTANCE hinst,
                         unsigned long reason,
                         void*)
{
    return 1;
}

/*----------------------------------------------------------------*/
int __stdcall TestPassCharPtr(LPSTR szString, LONG cSize)
{
    MessageBox(0, szString, "Inside PassCharPtr.dll", 0);
    char buffer[] = "abcdefghijklmnopqrstuvwxyz";
    if (cSize > strlen(buffer))
    {
        strcpy(szString,buffer);
        return strlen(buffer);
    }
    return -cSize;
}
为了测试我的DLL,我创建了一个简单的VB6应用程序:
Private Declare Function TestPassCharPtr Lib "PassCharPtr" (ByVal buffer As String, ByVal lSize As Long) As Long
Private Sub btnTest_Click()
Dim sBuffer As String
Dim lResult As Long
    sBuffer = "This is a very long string!!!!!!!!!"
    lResult = TestPassCharPtr(sBuffer, Len(sBuffer))
Debug.Print "Result: "; lResult
Debug.Print "Buffer: "; Left(sBuffer, lResult)
End Sub

一切都很好。现在,这是我在VS2010中尝试访问这个函数的c#测试项目:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
namespace TestPassCharPtr
{
    class Program
    {
        /// <summary>
        /// Define DLL and function to call. Setup data types for return value and arguments
        /// and setup values to pass into function.
        /// 
        /// All data types should be declared using C'C++ names to facilitate using
        /// existing Win32 API documentation to define function calls.
        /// 
        /// When passing a string to a function call, enclose the string in quotes ("")
        /// 
        /// </summary>
        /// <param name="args">Unused</param>
        static void Main(string[] args)
        {
            string fileName = "PassCharPtr.dll";
            string funcName = "TestPassCharPtr";
            string returnType = "int";
            // comma-delimited list of argument data types
            // using this declaration successfully passes string in
            // but generates exception when passing string back!
            string argTypesList = "char[], int";  
            // using this declaration fails to pass string in, but does
            // not generate an exception when passing string back!
            //string argTypesList = "LPSTR, int";              
            // comma-delimited list of argument values  
            string argValuesList = "'"This is a very long string!!!!'", 30";   
            TestDLLFunction(fileName, funcName, returnType, argTypesList, argValuesList);
            MessageBox.Show("Done");
        }
        /// <summary>
        /// Calls a DLL function.
        /// 
        /// Reference: http://www.pcreview.co.uk/forums/calling-native-c-function-using-definepinvokemethod-and-returning-calculated-value-pointer-reference-t2329473.html
        /// 
        /// </summary>
        /// <param name="dllFilename">Filename of DLL (excluding path!)</param>
        /// <param name="entryPoint">Function name</param>
        /// <param name="retType">Return value data type</param>
        /// <param name="argTypesList">Comma-delimited list of argument data types</param>
        /// <param name="argValuesList">Comma-delimited list of argument values</param>
        private static void TestDLLFunction(string dllPath, string entryPoint, string retType, string argTypesList, string argValuesList)
        {
            Type returnType = null;
            Type[] argTypesArray = null;
            object[] argValuesArray = null;
            object returnValue = null;
            // get return data type
            returnType = Type.GetType(ConvertToNetType(retType));
            // get argument data types for the function to call
            string[] argTypes = argTypesList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            if (argTypes.Length > 0)
            {
                // create a list of data types for each argument
                List<Type> listArgTypes = new List<Type>();
                foreach (var argType in argTypes)
                {
                    string netType = ConvertToNetType(argType);
                    string byRef = IsPointer(argType) ? "&" : "";
                    listArgTypes.Add(Type.GetType(netType + byRef));
                }
                // convert the list to an array
                argTypesArray = listArgTypes.ToArray<Type>();
                // get values to pass as arguments to the function
                string[] argValues = argValuesList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                // remove quotes from strings
                for (int i = 0; i < argValues.Length; i++)
                {
                    argValues[i] = argValues[i].Replace("'"", "").Trim();
                }
                argValuesArray = argValues.ToArray<object>();
                // verify argument data types count and argument values count match!
                if (argValuesArray.Length != argTypesArray.Length)
                {
                    Console.WriteLine(string.Format("The number of parameter types ({0}) does not match the number of parameter values ({1}).", argTypesArray.Length, argValuesArray.Length));
                    return;
                }
                // convert all argument values to the proper data types
                for (int i = 0; i < argValuesArray.Length; i++)
                {
                    if (argTypesArray[i] == Type.GetType("System.IntPtr&"))
                    {
                        argValuesArray[i] = (IntPtr)0;
                    }
                    else
                    {
                        argValuesArray[i] = ConvertParameter(argValuesArray[i], argTypesArray[i]);
                    }
                }
            }
            else
            {
                argTypesArray = null;
                argValuesArray = null;
            }
            // Create a dynamic assembly and a dynamic module
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = dllPath;
            AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("tempModule");
            // Dynamically construct a global PInvoke signature using the input information
            MethodBuilder dynamicMethod = dynamicModule.DefinePInvokeMethod(entryPoint, dllPath,
                MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.PinvokeImpl,
                CallingConventions.Standard, returnType, argTypesArray, CallingConvention.Winapi, CharSet.Ansi);
            // Add PreserveSig to the method implementation flags. NOTE: If this line
            // is commented out, the return value will be zero when the method is invoked.
            dynamicMethod.SetImplementationFlags(dynamicMethod.GetMethodImplementationFlags() | MethodImplAttributes.PreserveSig);
            // This global method is now complete
            dynamicModule.CreateGlobalFunctions();
            // Get a MethodInfo for the PInvoke method
            MethodInfo mi = dynamicModule.GetMethod(entryPoint);
            // Invoke the function
            try
            {
                returnValue = mi.Invoke(null, argValuesArray);
            }
            catch (Exception ex)
            {
                Console.WriteLine(string.Format("Error: {0}", ex.Message));
                if (ex.InnerException != null)
                {
                    Console.WriteLine(string.Format("  Error: {0}", ex.InnerException.Message));
                }
            }
            if (returnValue != null)
            {
                Console.WriteLine(string.Format("Return value: {0}", returnValue.ToString()));
            }
            if (argValuesArray != null)
            {
                for (int i = 0; i < argValuesArray.Length; i++)
                {
                    if (argValuesArray[i] != null)
                    {
                        Console.WriteLine(string.Format("Argument {0}: {1}", i, argValuesArray[i].ToString()));
                    }
                }
            }
        }
        /// <summary>
        /// Converts a string to another data type.
        /// </summary>
        /// <param name="value">Value to be converted</param>
        /// <param name="dataType">Data type to convert to</param>
        /// <returns>Converted value</returns>
        private static object ConvertParameter(object value, Type dataType)
        {
            // determine the base data type (remove "&" from end of "by reference" data types)
            string baseDataType = dataType.ToString();
            if (baseDataType.EndsWith("&"))
            {
                baseDataType = baseDataType.Substring(0, baseDataType.Length - 1);
            }
            return Convert.ChangeType(value, Type.GetType(baseDataType));
        }
        /// <summary>
        /// Determines whether the indicated native data type is a pointer
        /// </summary>
        /// <param name="dataType">Native (unmanaged) data type</param>
        /// <returns>true if data type is a pointer; false otherwise</returns>
        private static bool IsPointer(string dataType)
        {
            string lowerDataType = dataType.ToLower();
            if (lowerDataType.StartsWith("lp") || lowerDataType.EndsWith("*"))
            {
                return true;
            }
            return false;
        }
        /// <summary>
        /// Convert unmanaged data type names to .NET data type names
        ///
        /// (for simplicity, all types unused by this example were removed)
        ///
        /// </summary>
        /// <param name="type">Unmanaged data type name</param>
        /// <returns>Corresponding .NET data type name</returns>
        private static string ConvertToNetType(string type)
        {
            string lowerType = type.ToLower();
            if (lowerType.Contains("int"))
            {
                return "System.Int32";
            }
            else if (lowerType.Contains("lpstr"))
            {
                return "System.IntPtr";
            }
            else if (lowerType.Contains("char[]"))
            {
                return "System.String";
            }
            return "";
        }
    }
}

如果我将第一个参数声明为char[] (System.String),我可以成功地将字符串传递给函数,但是当DLL试图用要返回的字符串替换该字符串时,它会生成一个异常(访问受保护的内存)。

如果将第一个参数声明为LPSTR (System.IntPtr),则不能将字符串传递给函数。然而,在调用返回时,argValuesArray[0]包含了一个看起来像地址的东西。我还没能弄清楚如何将该地址转换为返回的字符串。我已经尝试使用String mvValue = Marshal.PtrToStringAnsi((IntPtr)argValuesArray[0]),但这返回一个空字符串。

这段代码中仍然有很多漏洞,但我希望总的想法足够清楚。谁能告诉我什么数据类型的第一个参数应该声明,以便能够传入和传出这个函数成功的字符串,以及如何做任何必要的数据类型的转换?

使用MethodInfo.Invoke在Win32 DLL和c#之间传递LPSTR

LPSTR通常被编组为StringBuilder。