我知道当调用一个非托管方法接受来自C#的char *参数时,可以传递一个StringBuilder并让非托管C代码修改它。但是,您必须知道要将哪些数据放入StringBuilder,因此您可以传递正确大小的缓冲区。 我找到了许多线程来帮助解决这个问题。
但是,我有一个接受char **参数的C方法。
这允许像check_if_encrypted
这样的方法(如下所示)提供malloc
错误消息,而调用方法不必知道为错误消息缓冲区分配多少空间,方法是使用类似于check_if_encrypted
中的以下代码:*strTheCharStarStarArgument = strLocalMallocdErrorMessage
,并通过传递&strErrorMessage
strErrorMessage
char*
C#中的这种方法应该使用什么DLLImport签名?
例如,我有以下C代码:
部首:
extern __declspec(dllexport) KeyFile *create_source_key_file_from_path(char *strPath);
extern __declspec(dllexport) int check_if_encrypted(KeyFile *kfSourceKey, int ktSourceKeyType, const char **strErrorMessage);
主要代码:
KeyFile *create_source_key_file_from_path(char *strPath) {
/* Snip */
}
int check_if_encrypted(KeyFile *kfSourceKey, int ktSourceKeyType, const char **strErrorMessage) {
/* Snip */
char* strLocalErrorMessage = (char*)malloc(sizeof(char)*17);
strcpy(strLocalErrorMessage,"This is an error");
*strErrorMessage = strLocalErrorMessage;
/* Snip */
}
/* Snip */
int main(int argc, char *argv[]) {
KeyFile *kfSource;
char *strErrorMessage;
const char **strErrorMessageP = &strErrorMessage;
int intIsEncrypted;
kfSource = create_source_key_file_from_path("test.dat");
intIsEncrypted = check_if_encrypted(kfSource,0,strErrorMessageP);
if (strErrorMessage != NULL) {
/* Handle error here. */
}
else if (intIsEncrypted == 1) {
/* Handle encrypted file here. */
}
else {
/* Handle unencrypted file here. */
}
}
我在使用check_if_encrypted非托管方法在C#中复制此main方法的尝试如何在调用check_if_encrypted时导致AccessViolationException(尝试读取或写入受保护的内存...)。
[DllImport("PPKConverter.exe", SetLastError = true, CallingConvention = CallingConvention.Cdecl, CharSet=CharSet.Ansi)]
public extern static IntPtr create_source_key_file_from_path([MarshalAs(UnmanagedType.LPStr)] StringBuilder strPath);
[DllImport("PPKConverter.exe", SetLastError = true, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public extern static int check_if_encrypted(IntPtr kfSourceKey, int ktSourceKeyType, [MarshalAs(UnmanagedType.LPStr)] ref StringBuilder sbErrorMessage);
[STAThread]
public static void Main(String[] args)
{
StringBuilder sbPath = new StringBuilder(@"test.dat");
StringBuilder sbErrorMessage = new StringBuilder();
IntPtr ipKeyFile = create_source_key_file_from_path(sbPath); // This works
int intIsEncrypted = check_if_encrypted(ipKeyFile, 0, ref sbErrorMessage); // This throws the exception.
/* Snip */
}
我认为这是因为我在DLLImport签名中使用ref StringBuilder
不正确。
应该使用什么?
由于
答案 0 :(得分:1)
非托管代码返回给调用者的字符串通过调用malloc
进行分配。因此,p / invoke框架不能解除分配它。您还需要导出解除分配器以避免泄漏。为了使用p / invoke调用函数,你需要手动编组。
[DllImport("PPKConverter.exe", CallingConvention = CallingConvention.Cdecl)]
public extern static int check_if_encrypted(
IntPtr kfSourceKey,
int ktSourceKeyType,
out IntPtr sbErrorMessage
);
将IntPtr
中返回的sbErrorMessage
转换为调用Marshal.PtrToStringAnsi
的字符串。如上所述,您需要将sbErrorMessage
传递回解除分配器以避免泄漏。
我删除了SetLastError = true
,因为我怀疑你的非托管功能确实调用了SetLastError
。
由于create_source_key_file_from_path
收到一个字符串作为输入,所以你应该这样声明:
[DllImport("PPKConverter.exe", CallingConvention = CallingConvention.Cdecl,
CharSet=CharSet.Ansi)]
public extern static IntPtr create_source_key_file_from_path(string strPath);
您确定要链接到.exe文件中的函数吗?
我也想知道让非托管代码用malloc
分配字符串是否明智。这会强制您导出解除分配器。您可能最好要求调用者分配内存。或者,如果必须在非托管代码中分配,请使用COM堆之类的共享堆。这样C#marshaller可以释放内存。