调用C++ DLL函数从C#返回char*,无法使用DllImport()。

8

我有一个包含两个函数的c++ dll

const char* getVersionText1(void);
void        getVersionText2(const char** res);

这两个函数都返回描述产品版本的字符串。第一个函数将其作为const char*返回(意味着在内部分配和释放),而第二个函数获取char*指针并将其设置为指向描述版本的char*。

我想从C#中调用这些函数并显示文本。 我不能使用[dllimport ...]样式,因为调用函数的顺序很重要。我先调用构造函数,然后是getVersion,最后是析构函数。所以必须首先将dll加载到内存中。

请您提供打印这两个函数文本的几行代码。 我是C#的新手,请原谅如果您发现我的代码有问题 我尝试过

static class NativeMethods{
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string dllToLoad);
    [DllImport("kernel32.dll")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr hModule);
}
class Program{
    // Define function pointers to entires in DLL
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr getVersionText1();
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void getVersionText2(IntPtr );
    static void Main(string[] args){
        // Pointers to functions of DLL.
        getVersionText1 f1;
        getVersionText2 f2;
        IntPtr pDll = NativeMethods.LoadLibrary("p:\\my.dll");
        IntPtr pAddressOfFunctionToCall;
        pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "getVersionText1 ");
        f1 = (getVersionText1)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(getVersionText1));
        pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "getVersionText2 ");
        f2 = (getVersionText2)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(getVersionText2));

        // Here I don't know what to do. It doesn't work ????
        IntPtr verText = f1();
        String s = Marshal.PtrToStringAuto(verText);
        Console.WriteLine(s);   // Doesnt work
        // And how to use the second function. How do I sent a pointer to char* ???
    }
}

你确定 getVersionText1getVersionText2 是实际的导出函数名称吗?C++名称重整通常会更改您的导出名称。您可以使用任何DLL导出查看器检查导出的名称。 - Panda Pajama
我确定。我用C++编写了这个DLL。 - DanielHsH
你可以制作一个托管的C++/CLI包装器DLL,用作C++和C#代码之间的通信器。 - Zaid Amir
3个回答

14

如果 C++ 库函数返回 char*,则 C# 代码会将其视为 IntPtr,并且 Marshal.PtrToStringAnsi() 会将其转换为 C# string

IntPtr verText = f1();
string s = Marshal.PtrToStringAnsi(verText);
Console.WriteLine(s);

8

这是一种进行通话的方法。可能会有更短的替代方法,可以自动执行utf8 / string转换。

[DllImport("mydll.dll")]
public static extern IntPtr getVersionText1(void);
public string getVersionTextConst()
{
    IntPtr ptr = getVersionText1();
    // assume returned string is utf-8 encoded
    return PtrToStringUtf8(ptr);
}

[DllImport("mydll.dll")]
public static extern void getVersionText2(out IntPtr res);
public string getVersionTextConst()
{
    IntPtr ptr;
    getVersionText2(ptr);
    // assume returned string is utf-8 encoded
    String str = PtrToStringUtf8(ptr);
    // call native DLL function to free ptr
    // if no function exists, pinvoke C runtime's free()
    return str;
}

private static string PtrToStringUtf8(IntPtr ptr) // aPtr is nul-terminated
{
    if (ptr == IntPtr.Zero)
        return "";
    int len = 0;
    while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0)
        len++;
    if (len == 0)
        return "";
    byte[] array = new byte[len];
    System.Runtime.InteropServices.Marshal.Copy(ptr, array, 0, len);
    return System.Text.Encoding.UTF8.GetString(array);
}

感谢您提供的getVersionText1()使用方法!但是我无法定义getVersionText2(IntPtr* res),因为编译器会报错指针不能在安全上下文中使用。什么是安全上下文?我看到您使用了“unsafe”关键字,但我不确定是否允许在整个应用程序中使用它,因为它是安全的。 - DanielHsH
您可以从解决方案资源管理器 -> 属性 -> 构建 -> 允许不安全代码中启用使用不安全代码。 - simonc
我不能使用不安全代码,因为我的 DLL 嵌入在第三方应用程序中,我不能强制客户使用不安全编译。但解决方案是使用关键字“out IntPtr”代替“IntPtr*”。 - DanielHsH
@DanielHsH 谢谢,你的建议好多了。我已经相应地更新了我的答案。 - simonc

1
如果你需要更快的速度,你也可以进行不安全读取(unsafe read):
unsafe string GetString(IntPtr ptr) => new string((char*)ptr); // local function

IntPtr charPtr = f1();
var s = GetString(charPtr);

当您添加unsafe方法时,VS会询问您是否允许在项目中使用不安全的代码:请回答“是”。或手动选择“项目属性/生成/允许不安全代码”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接