使用C++头文件从C#调用dll

4
我试图调用一个我已经拥有C++头文件的dll中的方法。我从C#中调用该dll。输入是一个字符串,输出是二进制数据。以下三种方法中的任何一种都可能有效,但我不知道如何使它们完全起作用。C#声明由我自己完成,所以可能不正确。
1:我能够获取hGlobal,但我不知道如何从句柄中获取数据。
//CMBT_LL_WINAPI INT DLLPROC  LlConvertStringToHGLOBALW(LPCWSTR pszText, _PHGLOBAL phMemory);
 [DllImport("cmll15.dll", EntryPoint = "LlConvertStringToHGLOBALW", CharSet =   CharSet.Unicode, ExactSpelling = true)]
 private static extern int _LlConvertStringToHGlobal32(string text, ref IntPtr handle);

2:

[DllImport("cmll15.dll", EntryPoint = "LlConvertStringToBLOBW", CharSet = CharSet.Unicode, ExactSpelling = true)]
//CMBT_LL_WINAPI INT      DLLPROC  LlConvertStringToBLOBW(LPCWSTR pszText, _PUINT8 pBytes, UINT nBytes);
private static extern int _LlConvertStringToBLOBW(string text, ref IntPtr pBytes, UInt32 nBytes);

3:

[DllImport("cmll15.dll", EntryPoint = "LlConvertStringToStreamW", CharSet = CharSet.Unicode, ExactSpelling = true)]
//CMBT_LL_WINAPI INT      DLLPROC  LlConvertStringToStreamW(LPCWSTR pszText, _PISTREAM pStream);
private static extern int _LlConvertStringToStreamW(string text, ref IntPtr pStream);

更新,这是我认为最终会使用的代码。

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    private static extern UIntPtr GlobalSize(IntPtr hMem);

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    public static extern IntPtr GlobalLock(IntPtr handle);

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    public static extern IntPtr GlobalUnlock(IntPtr handle);

    [DllImport("cmll15.dll", EntryPoint = "LlConvertStringToHGLOBALW", CharSet = CharSet.Unicode, ExactSpelling = true)]
    private static extern int _LlConvertStringToHGlobal32(string text, ref IntPtr handle);

    private static void Main(string[] args)
    {
        IntPtr dataHandle = IntPtr.Zero;
        _LlConvertStringToHGlobal32(Contents, ref dataHandle);
        try
        {
            var size = (uint) GlobalSize(dataHandle);
            var array = new byte[size];
            IntPtr dataPtr = GlobalLock(dataHandle);
            try
            {
                Marshal.Copy(dataPtr, array, 0, array.Length);
            }
            finally
            {
                GlobalUnlock(dataPtr);
            }

            using (var fs = new FileStream("c:\\file.dat", FileMode.Create))
            {
                fs.Write(array, 0, array.Length);
            }
        }
        finally
        {
            Marshal.FreeHGlobal(dataHandle);
        }
    }

你有尝试编译和运行它们吗?请说明问题,以便我们能够理解。 - Nawaz
1
如果您已经指定了 CharSet.Unicode,为什么还要指定 ExactSpellingEntryPoint?这些是多余的。 - Konrad Rudolph
我不认为我的问题是关于从C++到C#的方法转换,但我不确定,我只是展示了它们,所以你可能能够发现任何错误。我认为C#签名是正确的,但我不知道如何调用方法或使用给定的输出。 - Karsten
2个回答

1

第一个应该是最容易启动的,因为这样留给调用者去确定所需的大小。但是,你如何知道分配的大小并不明显。也许是返回值。您可以始终使用PInvoke GlobalSize()从HGLOBAL句柄获取大小。您应该使用PInvoke GlobalLock()将句柄转换为指针,然后使用Marshal.CopyMemory()将其复制到byte[]中。通过调用GlobalUnlock()和Marshal.FreeHGlobal()来清理内存,并将其放在finally块中,以免出现泄漏。

对于第二个,您应该将第二个参数声明为byte[](而不是ref)。麻烦的是,您必须事先猜测数组的大小。故障模式将是猜测大小太小。

第三个需要一个COM IStream。将其声明为out System.Runtime.InteropServices.ComTypes.IStream。它的行为很像.NET Stream,您可以调用Seek来寻找开头,并调用Read来读取数据。

我会选择第一个,因为最不可能让您失望。


我认为我有解决方案。看来我确实需要调用GlobalLock,否则它对我没用。这是源代码,请随意改进。 IntPtr h = IntPtr.Zero; _LlConvertStringToHGlobal32(ContentsB, ref h); UIntPtr size = GlobalSize(h); var array = new byte[(int) size]; IntPtr globalLock = GlobalLock(h); Marshal.Copy(globalLock, array, 0, array.Length); - Karsten
1
你必须正确清理。GlobalUnlock和Marshal.FreeHGlobal。 - Hans Passant

-1

对于第一步: 您应该获取二进制数据输出的长度,然后使用以下Marshal.Copy方法: byte[] data = new byte[length]; Marshal.Copy(handle, data, 0, length);

对于第二步和第三步:您有什么问题?


关于第1个问题,我在发布后的一段时间内弄清楚了,但我不知道长度。关于第2个问题,我仍然不知道长度,因此无法将输入设置为nBytes。关于第3个问题,使用pStream = IntPtr.Zero调用该方法会失败,因此我假设它应该指向一个有效的IStream,我不知道如何创建这样的流,或者我是否完全正确。 - Karsten
如果你没有长度,那么答案就是否定的。在C/C++中,任何数组都需要一个大小,因此您必须设法拥有长度。如果dll的代码在您手中,则应添加一个长度输出参数,否则您应查找dll中是否有支持获取数据数组长度的函数。最后一种机会是,您应该知道字符串文本的编码方式,然后就可以提前知道长度(例如ASCII字符串,只需传递text.Length或者如果是UTF16,则传递text.Length * 2)。 - longbkit
请使用IntPtr pStream = Marshal.AllocHGlobal(字节数); 对于3。 - longbkit
关于 3:类型是 _PISTREAM,我认为它需要一个指向 IStream 对象的指针?我会尝试看看是否可以找到一些方法来确定长度。 - Karsten

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