从托管 C# 代码调用非托管 C++ 代码以生成脱机域加入 blob

Posted

技术标签:

【中文标题】从托管 C# 代码调用非托管 C++ 代码以生成脱机域加入 blob【英文标题】:Calling unmanaged c++ code from managed C# code to generate offline domain join blob 【发布时间】:2018-01-09 01:33:25 【问题描述】:

我正在编写一个程序来生成Offline Domain Join blob 并将其保存以备将来使用。可以使用命令提示符完成此操作。下面是一个示例命令,它将生成上述文件并将其保存在 D 盘上:

D:\djoin.exe /REUSE /PROVISION /DOMAIN MyDomain.MyCompany.com /MACHINE "user1-pc" /SAVEFILE blob.txt

更多信息:Offline Domain Join (Djoin.exe) Step-by-Step Guide

现在,我想向我的程序(用 C# 编写)添加一个方法来为我执行此功能。

这里的一个问题是,微软提供的 API 是 C++ API。我尝试使用PInvoke 在托管代码中使用 API。下面是我写的代码。

using System;
using System.Runtime.InteropServices;

namespace TestBlob

    public class Program
    
        static void Main(string[] args)
        
            String domain = "MyDomain.MyCompany.com";
            String machineName = "user1-pc";
            String machineAccoutOU = null;
            String dcName = "MyDomain";
            uint options = 1;
            IntPtr provisionBinData = IntPtr.Zero;
            IntPtr provisionBinDataSize = IntPtr.Zero;
            string blob = string.Empty;
            IntPtr pProvisionTextData = Marshal.StringToHGlobalUni(blob);

            uint status = ODJNativeMethods.NetProvisionComputerAccount(domain, machineName, machineAccoutOU, dcName, options, ref provisionBinData, ref provisionBinDataSize, ref pProvisionTextData);

            Console.WriteLine(status);
            Console.WriteLine(Marshal.PtrToStringUni(pProvisionTextData));
            Console.Read();
        
    

    public static class NativeMethods
    
        [DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern uint NetProvisionComputerAccount([In] String lpDomain,
                                                              [In] String lpMachineName,
                                                              [In] String lpMachineAccountOU,
                                                              [In] String lpDcName,
                                                              [In] uint dwOptions,
                                                              [In] [Out] ref IntPtr pProvisionBinData,
                                                              [In] [Out] ref IntPtr pdwProvisionBinDataSize,
                                                              [In] [Out] ref IntPtr pProvisionTextData);
    

当我运行应用程序时,它总是返回 87(显示在控制台上),快速搜索后发现是错误消息:参数无效。

我在这里做错了什么?我的 PInvoke 类型与本机语言 API 对应的类型不正确吗?

【问题讨论】:

您的最后三个选项似乎不正确 - 我认为这些应该是 ref byte[]ref uintref string,或者只有 3 个 IntPtrs.. 其实他们都是 IntPtr first,同样的错误信息。编组呢?我做对了吗?它甚至需要吗? @yaakov 我只需要使用我的 C# 应用程序获取 blob 文件。你知道我如何利用这个原生 DLL 并在 C# 程序中使用吗?在线参考实际上是零示例代码!我一直在尝试和寻找几个小时! 目前您无法判断错误是在您传递给函数的数据中,还是在您的 p/invoke 处理中。下一步是通过编写针对官方 SDK 编译的 C++ 程序来缩小范围,该程序成功。此时,您将拥有一个已知有效的数据模板。如果您的 C# 变体使用相同的数据失败,那么您知道问题在您的 pinvoke 中。 【参考方案1】:

最后 3 个参数被声明为 out,这意味着你不能初始化它们,而是传递正确的指针,以便函数可以为你分配东西。

另外,根据我阅读函数文档的理解,二进制文件和字符串文件是互斥的,所以假设你想取回二进制文件,那么你可以像这样定义 API([in] 是通常是隐含的):

    [DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
    public static extern int NetProvisionComputerAccount(
        string lpDomain,
        string lpMachineName,
        string lpMachineAccountOU,
        string lpDcName,
        int dwOptions,
        out IntPtr pProvisionBinData,
        out int pdwProvisionBinDataSize,
        IntPtr pProvisionTextData);

注意该函数不使用SetLastError,所以不要在声明中声明。

这是如何称呼它的:

        string domain = "MyDomain.MyCompany.com";
        string machineName = "user1-pc";
        string machineAccoutOU = null;
        string dcName = "MyDomain";
        // I suggest you don't use hardcoded values to be nice with readers
        const int NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT = 1;

        int status = NetProvisionComputerAccount(
            domain,
            machineName,
            machineAccoutOU,
            dcName,
            NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT,
            out IntPtr binData, // let the system allocate the binary thing
            out int binDataSize, // this will be the size of the binary thing
            IntPtr.Zero); // we don't use this one here, pass null

我无法进一步测试(我收到错误 1354,我认为这在我的上下文中是正常的)。

请注意,该文档没有说明释放函数分配的内容(如果它分配了一些东西?有一些罕见的 Windows API 使用他们拥有的静态缓冲区)。我认为您应该在完成所有工作后在 binData 上调用 NetApiBufferFree,但这只是一个猜测。

【讨论】:

非常感谢!这实际上有效:) 并感谢您提到解除分配!我会看看我应该如何处理。【参考方案2】:

这可能是您的文本文件的编码 - 如果您将其保存为 ANSI 并将其作为 Unicode 使用,它可能无法正常工作。

使用与您上面的代码相对应的这些语句:

IntPtr pProvisionTextData = Marshal.StringToHGlobalAnsi(blob);

[DllImport("user32", CharSet=CharSet::Ansi)] 

希望对你有帮助。

【讨论】:

Win32函数定义为LPCWSTR,即unicode。 是的。我仍然尝试过,但没有通过。感谢您的评论。

以上是关于从托管 C# 代码调用非托管 C++ 代码以生成脱机域加入 blob的主要内容,如果未能解决你的问题,请参考以下文章

从非托管 c++ 调用 C# 函数(通过托管包装器)

让非托管 c++ 代码调用调用 c# 代码的托管 c++ 代码

调试从非托管 C++ 调用的托管 .NET 代码

尝试从 C++/CLI 调用非托管 C++ 时解决错误

将 HBITMAP 句柄从非托管代码传递到托管代码以创建 System.Drawing.Bitmap 的安全性

从 C# 调用非托管 C++ VS 6.0 MFC dll