SQLSERVER 居然也能调 C# 代码 ?
一:背景
1. 讲故事
前些天看到一个奇怪的 Function 函数,调用的是 C# 链接库中的一个 UserLogin 方法,参考代码如下:
CREATE FUNCTION dbo.clr_UserLogin
(
@name AS NVARCHAR(100),
@password AS NVARCHAR(100)
)
RETURNS INT
AS
EXTERNAL NAME asmXXX.[xxx.CLRFunctions].UserLogin;
GO
这就让我产生了很大的兴趣,众所周知 SQLSERVER 是 C++ 写的,那这里的 C++ 怎么和 C# 打通呢? 而且 C# 是一门托管语言,需要 JIT 将其 native 化,这个 JIT 又在哪里呢? 带着这些疑问一起研究下吧。
二:互通原理研究
1. 一个简单的例子
首先写一段简单的 C# 代码,然后把它编译成 dll。
namespace AQMN.Bussiness
{
public class UserFunctions
{
public static string UserLogin(string username, string password)
{
var random = new Random();
var isSuccess = random.Next() % 2 == 0;
return isSuccess ? "登录成功" : "登录失败";
}
}
}
接下来需要做的就是数据库参数配置,开启 CLR 支持,并且指定某个数据库支持 unsafe 模式。
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE;
GO
ALTER DATABASE MyTestDB SET TRUSTWORTHY ON;
GO
为了能够调到 C# 的 UserLogin 方法,需要 SQLSERVER 先导入这个程序集,然后再以 Function 映射其中方法即可,参考代码如下:
CREATE ASSEMBLY clr_AQMN_Bussiness
FROM 'D:\net6\SQLCrawl\AQMN.Bussiness\bin\Debug\AQMN.Bussiness.dll'
WITH PERMISSION_SET = UNSAFE;
GO
CREATE FUNCTION dbo.clr_UserLogin
(
@username AS NVARCHAR(100),
@password AS NVARCHAR(100)
)
RETURNS NVARCHAR(100)
AS
EXTERNAL NAME clr_AQMN_Bussiness.[AQMN.Bussiness.UserFunctions].UserLogin;
GO
创建完了之后,可以观察 assembly 开头的几个系统视图。
SELECT * FROM sys.assemblies
SELECT * FROM sys.assembly_files;
SELECT * FROM sys.assembly_modules;

看起来没啥问题,接下来调用一下刚才创建的 clr_UserLogin 函数。
SELECT dbo.clr_UserLogin(N'jack',N'123456') AS 'State'
GO 10

从图中看登录结果是随机的,说明 C# 的 Random 函数起到了作用,非常有意思。
2. WinDbg 观察
从案例的运行结果看,推测在 SQLSERVER 中应该承载了一个 CLR 运行环境,那是不是这样呢?可以用 WinDbg 附加到 sqlservr.exe 进程,用 lm 观察下模块加载情况。
0:092> lm
start end module name
...
00007ff8`d3960000 00007ff8`d3aaf000 clrjit (deferred)
00007ff8`de040000 00007ff8`deb02000 clr (deferred)
...
0:092> !eeversion
4.8.4300.0 free
Server mode with 12 gc heaps
SOS Version: 4.8.4300.0 retail build
从输出看果然加载了 clr 和 clrjit 动态链接库,当前还是 gc server 模式,哈。
接下来再验证一个问题,既然 clr_UserLogin 函数会显示 登录成功/登录失败,那必然会调用 C# 的 UserLogin 方法,可以在 WinDbg 中对 UserLogin 方法下一个断点观察一下这个调用过程。
0:090> !name2ee AQMN.Bussiness!AQMN.Bussiness.UserFunctions.UserLogin
Module: 00007ff87ee37988
Assembly: AQMN.Bussiness, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Token: 0000000006000001
MethodDesc: 00007ff87ee38020
Name: AQMN.Bussiness.UserFunctions.UserLogin(System.String, System.String)
JITTED Code Address: 00007ff87ec560d0
0:090> bp 00007ff87ec560d0
0:090> g
从输出信息看 UserLogin 方法已经被 JIT 过了,用 bp 下完断点之后,继续 g,然后在 SSMS 上再次执行查询就可以成功命中啦。
0:090> k
# Child-SP RetAddr Call Site
00 000000df`1557ae48 00007ff8`7ee500b6 0x00007ff8`7ec560d0
01 000000df`1557ae50 00007ff8`7ec55ef1 0x00007ff8`7ee500b6
02 000000df`1557aeb0 00007ff8`de04222e 0x00007ff8`7ec55ef1
03 000000df`1557af00 00007ff8`a2b79ff3 clr!UMThunkStub+0x6e
04 000000df`1557af90 00007ff8`a2b741bd sqllang!CallProtectorImpl::CallWithSEH<AppDomainCallTraits,void,FunctionCallBinder_3<void,void (__cdecl*)(void (__cdecl*)(void * __ptr64),void * __ptr64,enum ESqlReturnCode * __ptr64),void (__cdecl*)(void * __ptr64),void * __ptr64,enum ESqlReturnCode * __ptr64> const >+0x23
05 000000df`1557afc0 00007ff8`a2b6bfc4 sqllang!CallProtectorImpl::CallExternalFull<AppDomainUserCallTraits,void,FunctionCallBinder_3<void,void (__cdecl*)(CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64),CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64> const >+0x2dd
06 000000df`1557b130 00007ff8`a2bda602 sqllang!CAppDomain::InvokeClrFn+0xd4
07 000000df`1557b1d0 00007ff8`aef51ee7 sqllang!UDFInvokeExternalImpl+0xb72
08 000000df`1557b7e0 00007ff8`9de52e24 sqlTsEs!CEsExec::GeneralEval4+0xe7
09 000000df`1557b8b0 00007ff8`9de52d64 sqlmin!CQScanProjectNew::EvalExprs+0x18f
0a 000000df`1557b920 00007ff8`9ddd8759 sqlmin!CQScanProjectNew::GetRow+0x98
0b 000000df`1557b970 00007ff8`9ddc73de sqlmin!CQScanLightProfileNew::GetRow+0x19
0c 000000df`1557b9a0 00007ff8`a25e51d7 sqlmin!CQueryScan::GetRow+0x80
0d 000000df`1557b9d0 00007ff8`a32a78b2 sqllang!CXStmtQuery::ErsqExecuteQuery+0x3d8
0e 000000df`1557bb40 00007ff8`a2bc2451 sqllang!CXStmtSelect::XretDoExecute+0x342
0f 000000df`1557bc10 00007ff8`a2b733d3 sqllang!UM_LoopbackForStatementExecution+0x191
10 000000df`1557bd00 00007ff8`de48e940 sqllang!AppDomainCallback<FunctionCallBinder_5<void,void (__cdecl*)(CXStmtQuery * __ptr64,CCompExecCtxtStmt const * __ptr64,CMsqlExecContext * __ptr64,unsigned long * __ptr64,enum ESqlReturnCode * __ptr64),CXStmtQuery * __ptr64,CCompExecCtxtStmt const * __ptr64,CMsqlExecContext * __ptr64,unsigned long * __ptr64,enum ESqlReturnCode * __ptr64> >+0x23
11 000000df`1557bd40 00007ff8`de48e193 clr!ExecuteInAppDomainHelper+0x40
12 000000df`1557bd80 00007ff8`a2b79f39 clr!CorHost2::ExecuteInAppDomain+0x3a0
13 000000df`1557c0a0 00007ff8`a2b73a86 sqllang!CallProtectorImpl::CallWithSEH<AppDomainCallTraits,long,MethodCallBinder_3<long,ICLRRuntimeHost,long (__cdecl ICLRRuntimeHost::*)(unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64) __ptr64,unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64> >+0x29
14 000000df`1557c0d0 00007ff8`a2b6c2d0 sqllang!CallProtectorImpl::CallExternalFull<AppDomainCallTraits,long,MethodCallBinder_3<long,ICLRRuntimeHost,long (__cdecl ICLRRuntimeHost::*)(unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64) __ptr64,unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64> >+0x186
15 000000df`1557c170 00007ff8`a32a72f4 sqllang!CAppDomain::LoopbackForStatementExecution+0x180
16 000000df`1557c230 00007ff8`a32a79ad sqllang!CXStmtQuery::XretCLRExecute+0x104
17 000000df`1557c2a0 00007ff8`a25e4a65 sqllang!CXStmtSelect::XretExecute+0x4a
18 000000df`1557c370 00007ff8`a25e44a8 sqllang!CMsqlExecContext::ExecuteStmts<1,1>+0x8f2
19 000000df`1557cf10 00007ff8`a25e3a2c sqllang!CMsqlExecContext::FExecute+0x936
1a 000000df`1557def0 00007ff8`a25ee67b sqllang!CSQLSource::Execute+0xc5c
1b 000000df`1557e3d0 00007ff8`a25ed815 sqllang!process_request+0xca6
1c 000000df`1557ead0 00007ff8`a25ed5ef sqllang!process_commands_internal+0x4b7
1d 000000df`1557ec00 00007ff8`b1e46523 sqllang!process_messages+0x1d6
1e 000000df`1557ede0 00007ff8`b1e46e6d sqldk!SOS_Task::Param::Execute+0x232
1f 000000df`1557f3e0 00007ff8`b1e46c75 sqldk!SOS_Scheduler::RunTask+0xa5
20 000000df`1557f450 00007ff8`b1e6b160 sqldk!SOS_Scheduler::ProcessTasks+0x39d
21 000000df`1557f570 00007ff8`b1e6aa5b sqldk!SchedulerManager::WorkerEntryPoint+0x2a1
22 000000df`1557f640 00007ff8`b1e6afa4 sqldk!SystemThreadDispatcher::ProcessWorker+0x3ed
23 000000df`1557f940 00007ff8`f6d86fd4 sqldk!SchedulerManager::ThreadEntryPoint+0x3b5
24 000000df`1557fa30 00007ff8`f865cec1 KERNEL32!BaseThreadInitThunk+0x14
25 000000df`1557fa60 00000000`00000000 ntdll!RtlUserThreadStart+0x21
果然是一个 request 请求,然后达到了托管方法 UserLogin,顶部的三行线程栈可以用 !clrstack 具意下。
0:090> !clrstack
OS Thread Id: 0x6df4 (90)
Child SP IP Call Site
000000df1557ae48 00007ff87ec560d0 AQMN.Bussiness.UserFunctions.UserLogin(System.String, System.String)
000000df1557ae50 00007ff87ee500b6 DynamicClass.SQLCLR_Eval(IntPtr, IntPtr, IntPtr)
000000df1557aeb0 00007ff87ec55ef1 DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int64, Int64, Int64)
000000df1557bf18 00007ff8de04222e [ContextTransitionFrame: 000000df1557bf18]
三:总结
SQLSERVER 内嵌了 CLR,让 sqlservr 进程成了一种托管和非托管的混合环境,不知道是好事还是坏事,在我的分析旅程中这种混合环境下看过太多的堆破坏问题,但不管怎么说,托管的 C#,VB,F# 可以助 SQLSERVER 更加强大。
SQLSERVER 居然也能调 C# 代码 ?的更多相关文章
- Sqlserver 存储过程中结合事务的代码
Sqlserver 存储过程中结合事务的代码 --方式一 if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[ ...
- PHP post调接口代码
PHP post调接口代码 /** * $url:接口地址 * $data:数组参数 **/ function postData($url, $data) { $ch = curl_init (); ...
- sqlserver profiler 抓出来作业的代码 SQLAgent - TSQL JobStep,二进制作业名字转化为字段串作业名字,job_id
sqlserver 中 profiler 抓出来不少是作业中的代码,applicationname 类似于 SQLAgent - TSQL JobStep (Job 0x94B9B5C016E8D94 ...
- 连接SQLServer的增删改查方法代码
在Visual C++中用ADO进行数据库编程 1. 生成应用程序框架并初始化OLE/COM库环境 创建一个标准的MFC AppWizard(exe)应用程序CADOConnection,然后在使用A ...
- 新西达电调初始化代码,使用nodejs ffi技术调用wiringpi,代码使用typescript编写
这是我设计的F450四轴飞行器飞控代码的一部分 运行在orangepi-zero上,操作系统是armbian,思路是使用node-ffi调用wiringpi的so库与GPIO通信,然后控制端逻辑代码使 ...
- sqlserver表分区与调优与行列转换
转自: http://www.cnblogs.com/knowledgesea/p/3696912.html http://www.open-open.com/lib/view/open1418462 ...
- ios调打电话代码
// 定义点击拨号按钮时的操作 - (void)callAction{ NSString *number = @"";// 此处读入电话号码 // NSString *num = ...
- 你在AutoHotKey面前居然敢比调音量 - imsoft.cnblogs
当你正在电脑游戏中酣战之际.或者正沉浸在动作大片紧张激烈的情节中.或者正在全神贯注的聆听优美动听音乐……,在这些场景中,如果你需要迅速对音量进行调节(例如增大减小音量,或者静音)怎么办?难道返回Win ...
- C# 连接SQLServer数据库自动生成model类代码
Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Threading ...
- C#—连接SQLserver数据库,并执行查询语句代码
//字段ArticleID,ArticleName,ArticleNumber,Unit,Weight,Price,Currency,IsIuggage,IsQuarantine string str ...
随机推荐
- 🔥支持 Java 19 的轻量级应用开发框架,Solon v1.10.4 发布
Java 轻量级应用开发框架.可用来快速开发 Java 应用项目,主框架仅 0.1 MB. 相对于 Spring Boot 和 Spring Cloud 的项目: 启动快 5 - 10 倍. (更快) ...
- Docker | 使用dockerfile生成镜像,清理docker空间
用dockerfile生成镜像并挂载数据卷 编写dockerfile文件 创建dockerfile01 文件 # 基础镜像 FROM centos VOLUME ["volume01&quo ...
- Codeforces Round #801 (Div. 2) C(规律证明)
Codeforces Round #801 (Div. 2) C(规律证明) 题目链接: 传送门QAQ 题意: 给定一个\(n * m\)的矩阵,矩阵的每个单元的值为1或-1,问从\((1,1)\)开 ...
- 6.RabbitMQ系列之direct直连交换器
我们通过fanout exchange(扇型交换机)实现生产者发送一个消息,这个消息同时被传送给所有队列.但是有时我们不希望所有的消息都被所有队列接收,我们希望可以指定类型为a的消息只能被队列A接收, ...
- 【算法】基础DP
参考资料 背包九讲 一.线性DP 如果现在在状态 i 下,它上一步可能的状态是什么. 上一步不同的状态依赖于什么. 根据上面的分析,分析出状态和转移方程.注意:dp 不一定只有两维或者一维,一开始设计 ...
- 驱动开发:内核测试模式过DSE签名
微软在x64系统中推出了DSE保护机制,DSE全称(Driver Signature Enforcement),该保护机制的核心就是任何驱动程序或者是第三方驱动如果想要在正常模式下被加载则必须要经过微 ...
- 论文笔记 - Active Learning by Acquiring Contrastive Examples
Motivation 最常用来在 Active Learning 中作为样本检索的两个指标分别是: 基于不确定性(给模型上难度): 基于多样性(扩大模型的推理空间). 指标一可能会导致总是选到不提供有 ...
- java安全之CC1浅学(1)
前言 由于CC链还是比较复杂的,我们可以先看命令执行的部分payload之后再加上反序列化部分组成一个完整的payload 调试一 项目导入依赖,这里使用3.1版本 <!-- https://m ...
- 【网络】博客网站搭建之Typecho(命令版)
目录 前言 个人博客系统筛选 内网穿透 安装nginx 安装PHP 安装mysql Typecho 环境安装 参考 安装typecho Nginx与PHP进行连接配置&指定博客路径 验证 配置 ...
- 打不开 github 的方法与推荐基于Vue3与Element plus的后台管理系统
一.打不开 github 的方法 1.打开本机 hosts 文件(C:\Windows\System32\drivers\etc) 2.然后在 hosts 文件里的末尾放入一下两个 IP 地址: # ...