使用来自托管C#代码的const char **参数调用非托管C方法

时间:2014-10-30 16:14:25

标签: c# pinvoke unmanaged access-violation

我知道当调用一个非托管方法接受来自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不正确。 应该使用什么?

由于

1 个答案:

答案 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可以释放内存。