从 C# 调用带有 C++ 标头的 dll

Posted

技术标签:

【中文标题】从 C# 调用带有 C++ 标头的 dll【英文标题】:Call dll with C++ header from C# 【发布时间】:2011-01-12 08:57:02 【问题描述】:

我正在尝试调用我拥有 C++ 标头的 dll 中的方法。我正在从 C# 调用 dll。输入是字符串,输出是二进制数据。以下 3 种方法中的任何一种都可能有效,我只是不知道如何使它们中的任何一种一直有效。 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);
        
    

【问题讨论】:

你试过编译和运行它们吗?指定问题以便我们理解。 如果您已经指定了CharSet.Unicode,为什么还要指定ExactSpellingEntryPoint?这些都是多余的。 我不认为我的问题是关于从 C++ 到 C# 的方法的翻译,但我不知道,我只是展示了它们,所以你可能会发现任何错误。我认为 C# 签名是正确的,但我现在不知道调用方法或使用方法的给定输出 【参考方案1】:

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

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

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

我会选择第一个,最不可能在你脸上炸毁。

【讨论】:

我想我有解决办法。看来我确实需要调用 GlobalLock,否则它对我不起作用。这是源代码,请随时改进。 IntPtr h = IntPtr.Zero; _LlConvertStringToHGlobal32(ContentsB, ref h); UIntPtr 大小 = GlobalSize(h); var 数组 = 新字节 [(int) 大小]; IntPtr globalLock = GlobalLock(h); Marshal.Copy(globalLock, array, 0, array.Length); 你必须正确清理。 GlobalUnlock 和 Marshal.FreeHGlobal。【参考方案2】:

对于 1: 你应该有二进制数据输出的长度,然后使用 Marshal.Copy 方法如下: 字节[]数据=新字节[长度]; Marshal.Copy(handle, data, 0, length);

对于 2 和 3:您的问题是什么?

【讨论】:

大约 1,我在发布后的同时想出了这个,但我没有长度。大约2我仍然不知道长度,所以不能将输入设置为nBytes。大约 3,使用 pStream = IntPtr.Zero 调用该方法失败,所以我假设它应该指向一个有效的 IStream,我不知道如何创建这样一个,或者我是否在正确的轨道上。跨度> 1:如果你没有长度,那么答案是不可能的。在 C/C++ 中,任何数组都有大小,因此您必须设法拥有长度。如果 dll 的代码在你手中,你应该添加一个长度输出参数,否则你应该发现 dll 中是否有任何函数支持获取数据数组的长度。最后的机会,那就是你应该知道字符串文本的编码是什么,然后你可以事先知道长度(例如 ASCII 字符串然后只传递 text.Length 或者如果它是 UTF16 则传递 text.Length * 2)。跨度> 对于 3:请使用 IntPtr pStream = Marshal.AllocHGlobal(字节数); 关于3:类型是_PISTREAM,我认为它需要一个指向IStream对象的指针?我会试着看看我是否能以某种方式计算出长度..

以上是关于从 C# 调用带有 C++ 标头的 dll的主要内容,如果未能解决你的问题,请参考以下文章

从 C# 调用 C++ 代码而不创建 dll?

创建 C++ Dll,并从 C# 调用它

调用从 c# 返回 char* 的 c++ dll 函数。无法使用 DllImport()

从 c# 调用 c++ 库

从 C++ 调用 C# dll

从 C# 调用 C++ dll 并抛出 SEHException