使用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]),但这返回一个空字符串。
这段代码中仍然有很多漏洞,但我希望总的想法足够清楚。谁能告诉我什么数据类型的第一个参数应该声明,以便能够传入和传出这个函数成功的字符串,以及如何做任何必要的数据类型的转换?
LPSTR通常被编组为StringBuilder。