我试图通过C#程序集运行本机代码API(在C ++ .dll中),这样我就可以在SQL Server的CLR存储过程中使用某些API函数。我试图从C ++ dll使用的函数访问数据历史数据库中的原始数据,并以非托管类型返回数据。然后将它留给C#程序集来编组并将结果传递给SQL Server。
我没有C ++ dll的源代码,所以我真的不知道底层究竟发生了什么(它是第三方)。但是,我可以在C#控制台应用程序中访问这些API函数而不会出现问题(我依靠https://lennilobel.wordpress.com/2014/01/29/calling-c-from-sql-clr-c-code/来包装.NET中的C ++ dll)。我开发了一个工作的C#控制台应用程序,然后将其转换为类库,将类包装在“[Microsoft.SqlServer.Server.SqlProcedure]”中,并将程序集添加到UNSAFE模式下的所需SQL数据库中。我还确保在SQL服务器中启用了clr,并且在我正在使用的数据库中关闭了TRUSTWORTHY。
但是,当我尝试调用使用C#程序集的存储过程时,我遇到以下问题。
Location: AppDomain.cpp:2705
Expression: hr != E_POINTER
SPID: 66
Process ID: 3584
Msg 3624, Level 20, State 1, Procedure sp_direct_proficy_api, Line 0 [Batch Start Line 2]
A system assertion check has failed. Check the SQL Server error log for details. Typically, an assertion failure is caused by a software bug or data corruption. To check for database corruption, consider running DBCC CHECKDB. If you agreed to send dumps to Microsoft during setup, a mini dump will be sent to Microsoft. An update might be available from Microsoft in the latest Service Pack or in a Hotfix from Technical Support.
Msg 596, Level 21, State 1, Line 2
Cannot continue the execution because the session is in the kill state.
Msg 0, Level 20, State 0, Line 2
A severe error occurred on the current command. The results, if any, should be discarded.
我已经对系统断言检查进行了一些谷歌搜索,并且看到它们通常是由于数据库损坏而发生的。我运行了DBCC CHECKDB,一切都很好,所以这不是问题。我已经复制了Leonard的例子(来自上面的链接),这个例子与我用更简单的C ++ dll进行的过程基本相同。该示例没有发生错误,因此我认为SQL Server和C ++ API之间存在一些针对appdomain的竞争。
我的问题
这是我试图做的预期问题吗?我不太了解SQL Server在使用CLR存储过程时如何访问计算机内存和声明应用程序域,但似乎SQL Server和C ++ API之间存在一些有害的资源竞争。
下面显示的是C#程序集的两个部分(从C#harness调用C ++ dll以及存储过程要访问的类)。
C#DLL从C ++导入
public class IHUAPI
{
const string DLLNAME = "IHUAPI.dll";
static class IHU64
{
[DllImport(DLLNAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ihuConnect@16")]
public static extern ihuErrorCode ihuConnect(string server, string username, string password, out int serverhandle);
[DllImport(DLLNAME, CallingConvention = CallingConvention.StdCall, EntryPoint = "ihuReadRawDataByTime")]
public static extern ihuErrorCode ihuReadRawDataByTime(int serverhandle, string tagname, ref IHU_TIMESTAMP start, ref IHU_TIMESTAMP end, out int numberOfSamples, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4, ArraySubType = UnmanagedType.LPStruct)] out IHU_DATA_SAMPLE[] samples);
}
public static ihuErrorCode ihuConnect(string server, string username, string password, out int serverhandle)
{
return IHU64.ihuConnect(server, username, password, out serverhandle);
}
public static ihuErrorCode ihuReadRawDataByTime(int serverhandle, string tagname, IHU_TIMESTAMP start, IHU_TIMESTAMP end, out IHU_DATA_SAMPLE[] samples)
{
int numberOfSamples;
return IHU64.ihuReadRawDataByTime(serverhandle, tagname, ref start, ref end, out numberOfSamples, out samples);
}
}
C#程序集在存储过程中用于访问C ++ API
[Microsoft.SqlServer.Server.SqlProcedure]
public static void API_Query(string tagname, DateTime start_date, DateTime end_date)
{
int handle;
ihuErrorCode result;
result = IHUAPI.ihuConnect("houmseosprf007", "", "", out handle);
IHU_DATA_SAMPLE[] values;
IHU_TIMESTAMP start = new IHU_TIMESTAMP(start_date);
IHU_TIMESTAMP end = new IHU_TIMESTAMP(end_date);
ihuErrorCode result_api = IHUAPI.ihuReadRawDataByTime(handle, tagname, start, end, out values);
SqlMetaData[] md = new SqlMetaData[3];
md[0] = new SqlMetaData("tagname", SqlDbType.Text);
md[1] = new SqlMetaData("return_value", SqlDbType.NVarChar, 50);
md[2] = new SqlMetaData("timestamp", SqlDbType.DateTime);
SqlDataRecord row = new SqlDataRecord(md);
SqlContext.Pipe.SendResultsStart(row);
DateTime p;
string p2;
for (int i = 1; i < (values == null ? 0 : values.Length); i++)
{
using (IHU_DATA_SAMPLE sample = values[i])
{
if (sample.ValueDataType != ihuDataType.Array)
{
p = sample.TimeStamp.ToDateTime();
p2 = sample.ValueObject.ToString();
row.SetValue(0, tagname);
row.SetValue(1, p2);
row.SetValue(2, p);
}
else
{
p = sample.TimeStamp.ToDateTime();
ihuArrayValue aValue = (ihuArrayValue)Marshal.PtrToStructure(sample.Value.ArrayPtr, typeof(ihuArrayValue));
p2 = aValue.GetArrayValue.ToString();
row.SetValue(0, tagname);
row.SetValue(1, p2);
row.SetValue(2, p);
}
}
SqlContext.Pipe.SendResultsRow(row);
}
SqlContext.Pipe.SendResultsEnd();
}
答案 0 :(得分:1)
似乎SQL Server和C ++ API之间存在一些有害的资源竞争。
是。您不应该真正使用SQL CLR中的非托管DLL。 CLR托管代码是内存安全的,SQLCLR旨在保护SQL Server免受自定义托管代码引起的任何问题。但是,如果您使用非托管代码,则没有任何安全保证,并且可能(可能)您可以使SQL Server崩溃。
而是从短暂的客户端进程加载非托管DLL,与SQL Server进程分开,并且短暂存在,以便在客户端进程终止时可以清除第三方DLL的任何内存问题。 SSIS是托管和运行这样的东西的简单方法。
答案 1 :(得分:1)
这是我试图做的预期问题吗?
我不会说&#34;期待&#34; &#34; un 期待&#34;或者#34;不应该被&#34;惊讶。第三方库显然是在隔离时做的很好,但是当它从SQL Server的CLR主机中启动时是不可接受的。 SQL Server的CLR主机有充分的理由受到高度限制。
所以,你应该做的是主持这个第三方C ++库和原始(和工作)C#包装器作为在托管你要连接的服务的服务器上运行的Web服务,&#34; IHU&# 34 ;.然后,对于您的SQLCLR代码,请使用HttpWebRequest
和HttpWebResponse
来调用该Web服务。在SendResultsRow()
循环中解析响应XML / JSON。
请确保将更新的SQLCLR代码的PERMISSION_SET设置为EXTERNAL_ACCESS
,因为您不需要UNSAFE
:-),并且您仍然可以在查询批处理中获得相当即时的响应无需外壳通过xp_cmdshell
调用命令行或调用SSIS甚至调度作业来执行此操作。