PInvoke DllExport:结构封送处理失败

本文关键字:处理 失败 结构 DllExport PInvoke | 更新日期: 2023-09-27 18:31:13

我在编组和 PInvoke 方面有一些问题,我必须为现有的本机应用程序(Oracle Siebel CRM,呼叫中心集成接口)开发某种驱动程序。从我的角度来看,该应用程序的来源是黑匣子。以下是两个结构(如 API 描述中所定义)和正在使用它们的 API 函数的签名:

struct ISC_KeyValue /* Key-Value element */
{
ISC_STRING paramName;
ISC_STRING paramValue;
};
struct ISC_KVParamList /* List of Key-Value parameter */
{
struct ISC_KeyValue* dataItems;
long len;
};
ISCAPI ISC_RESULT CreateISCDriverInstance
/* in */(const ISC_STRING mediaTypeStr,
/* in */ const ISC_STRING languageCode,
/* in */ const ISC_STRING connectString,
/* in */ const struct ISC_KVParamList* datasetParams,
/* out */ ISC_DRIVER_HANDLE* handle);

ISC_STRING这里是宽字符C++字符串(2 字节 Unicode)。ISC_RESULT是类似于 HRESULT 类型的整数值。我在将ISC_KVParamList*数据集参数封送到CreateISCDriverInstance函数中时遇到问题,我需要帮助。

以下是我对如何编组这件事的看法:

   [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct IscKeyValue
    {
          [MarshalAs(UnmanagedType.LPWStr)]
          public string ParamName;
          [MarshalAs(UnmanagedType.LPWStr)]
          public string ParamValue;
    }
  [StructLayout(LayoutKind.Sequential)]
public struct IscKvParamList
{
    public IntPtr DataItems;
    [MarshalAs(UnmanagedType.I4)]
    public int Len;
}

以及函数本身:

    [DllExport("CreateISCDriverInstance", CallingConvention = CallingConvention.StdCall)]
    public static int CreateIscDriverInstance([MarshalAs(UnmanagedType.LPWStr)] string mediaTypeStr,
        [In,MarshalAs(UnmanagedType.LPWStr)] string languageCode,
        [In,MarshalAs(UnmanagedType.LPWStr)] string connectString,
        [In, Out] ref IscKvParamList dataParams, [Out] IntPtr handle)
        {
 var datasetParams =(IscKvParamList)Marshal.PtrToStructure(dataParams,typeof(IscKvParamList));
             // Some code here
            }

当我尝试执行 PtrToStructure 转换时,我得到异常,即"结构不得是值类"。

问题:如何改进数据编组参数参数

附言其他详细信息 :1.我确定字符集是Unicode的2.我不知道数组的大小,也无法按值封送它。3. 另外,我有很久以前在德尔福编写的此类驱动程序的示例实现:

TNamedParam = record 
Name: WideString;
Value: WideString; 
end; 
TNamedParamList = record
Params: packed array of TNamedParam;
Count: Cardinal;
end;
function CreateISCDriverInstance(const AMediaTypeStr, ALanguageCode, AConnectString: PWideChar; const AParams: TISCNamedParamList; out ADriverHandle: THandle):
HRESULT; begin try ADriverHandle := icISCCommunicationDriver.CreateInstance(AParams).Handle; 
Result := SC_EC_OK;
except Result := SC_EC_DRIVER_CREATION_ERR;
end;
end; 

和解析 AParams:

procedure JoinISCNamedParamList(const AParamList: TNamedParamList; var LParamList: TISCNamedParamList);
  var i:Integer;
begin
  LParamList.Count := AParamList.Count;
  SetLength(LParamList.Params, LParamList.Count);
  for I := 0 to Pred(AParamList.Count) do
  begin
    LParamList.Params[i].Name := PWideChar(AParamList.Params[i].Name);
    LParamList.Params[i].Value := PWideChar(AParamList.Params[i].Value);
  end;
end;

可以说这里的结构布局是顺序的。

PInvoke DllExport:结构封送处理失败

问题中的信息不完整。网络搜索显示的代码如下:

typedef wchar_t ISC_CHAR
typedef ISC_CHAR* ISC_STRING; 

这与你在问题中指出的内容相符。所以,让我们继续吧。但是,如果您可以提供可以在其中一个头文件中找到的信息,那就更好了。

天真地,你的结构应该是:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct IscKeyValue
{
    public string ParamName;
    public string ParamValue;
}
[StructLayout(LayoutKind.Sequential)]
public struct IscKvParamList
{
    public IscKeyValue[] DataItems;
    public int Len;
}

不需要您使用的MarshalAs,因为默认封送就足够了。

但是编组器不会处理结构内部的数组。所以我认为你需要这样做:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct IscKeyValue
{
    public IntPtr ParamName;
    public IntPtr ParamValue;
}
[StructLayout(LayoutKind.Sequential)]
public struct IscKvParamList
{
    public IntPtr DataItems;
    public int Len;
}

至于函数,它翻译为:

[DllExport("CreateISCDriverInstance", CallingConvention = CallingConvention.StdCall,
    CharSet = CharSet.Unicode, ExactSpelling = true)]
public static int CreateIscDriverInstance(
    string mediaTypeStr,
    string languageCode,
    string connectString,
    [In] ref IscKvParamList dataParams, 
    out IntPtr handle
);

现在的乐趣。准备IscKvParamList参数。

从一组IscKeyValue开始:

IscKeyValue[] KeyValueArr = new IscKeyValue[...];
for (int i = 0; i < KeyValueArr.Length; i++)
{
    KeyValueArr[i].ParamName = Marshal.StringToCoTaskMemUni(...);
    KeyValueArr[i].ParamValue = Marshal.StringToCoTaskMemUni(...);
}

现在IscKvParamList.我想我会固定数组。喜欢这个:

GCHandle ArrHandle = GCHandle.Alloc(KeyValueArr, GCHandleType.Pinned);
try
{
    IscKvParamList dataParams;
    dataParams.DataItems = ArrHandle.AddrOfPinnedObject();
    dataParams.Len = KeyValueArr.Length;       
    int retval = CreateIscDriverInstance(
         mediaTypeStr,
         languageCode,
         connectString,
         ref dataParams,
         out handle
    );
}
finally
{
    ArrHandle.Free();
}

您必须确保记住释放在调用 Marshal.StringToCoTaskMemUni 时分配的非托管内存。