0

我正在尝试将实现模糊匹配的现有 C DLL(显然是非托管的)集成到 SQL Server 作为用户定义函数(UDF)。UDF 是通过 CLR VB 项目实现的。近 20 年来,我一直使用这个 C 代码对文本文件进行字符串匹配,没有任何问题。它已在几乎所有平台上编译,从未崩溃或给出错误结果。曾经。到目前为止。

此 UDF 在 SQL SELECT 语句中的用法如下所示:

SELECT Field FROM Table WHERE xudf_fuzzy('doppler effect', Field) = 1;

xudf_fuzzy(Param1, Param2) = 1是魔法发生的地方。Param1 是我们试图匹配的线索词,而 Param2 是要测试的表中的字段。如果匹配在一定数量的错误内成功,则 UDF 返回 1,否则返回 0。到目前为止一切顺利。

下面是定义模糊 UDF 并调用 C DLL 的 CLR 代码:

Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlTypes
Imports System.Text
Imports Microsoft.SqlServer.Server
Imports System.Runtime.InteropServices


Partial Public Class fuzzy

<DllImport("C:\Users\Administrator\Desktop\fuzzy64.dll", _
           CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function setClue(ByRef clue As String, ByVal misses As Integer) As       Integer
End Function

<DllImport("C:\Users\Administrator\Desktop\fuzzy64.dll", _
           CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function srchString(ByRef text1 As String) As Integer
End Function


<Microsoft.SqlServer.Server.SqlFunction()> Public Shared Function _
        xudf_fuzzy(ByVal strSearchClue As SqlString, ByVal strStringtoSearch As SqlString) As Long

    Dim intMiss As Integer = 0
    Dim intRet As Integer
    Static Dim sClue As String = ""

    xudf_fuzzy = 0

    ' we only need to set the clue whenever it changes '
    If (sClue <> strSearchClue.ToString) Then
        sClue = strSearchClue.ToString
        intMiss = (Len(sClue) \ 4) + 1
        intRet = setClue(sClue, intMiss)
    End If

    ' return the fuzzy match result (0 or 1) '
    xudf_fuzzy = srchString(strStringtoSearch.ToString)

End Function

这是被调用的 C 代码的前端。STRCT 是所有全局存储所在的位置。

fuzzy.h
typedef struct {
short int INVRT, AND, LOWER, COMPL, Misses;
long int num_of_matched;
int D_length;
unsigned long int endposition, D_endpos;
unsigned long int Init1, NOERRM;
unsigned long int Mask[SYMMAX];
unsigned long int Init[MaxError];
unsigned long int Bit[WORDSIZE+1];
unsigned char prevpat[MaxDelimit];
unsigned char _buffer[Max_record+Max_record+256];
unsigned char _myPatt[MAXPAT];
} SRCH_STRUCT;


fuzzy.c
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <wtypes.h>
#include <time.h>
#include "fuzzy.h"

// call exports
__declspec(dllexport) int CALLBACK setClue(char**, int*);
__declspec(dllexport) int CALLBACK srchString(char**);

SRCH_STRUCT STRCT = { 0 };
int cluePrep(unsigned char []);
int srchMin(unsigned char [], int);

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

int CALLBACK setClue(char **pattern, int *misses)
{
    int i;
    unsigned char *p;

    // code to do initialization stuff, set flags etc. etc.    
        STRCT.Misses = (int)misses;
        p = &(STRCT._myPatt[2]);
        STRCT._myPatt[0] = '\n';
        STRCT._myPatt[1] = SEPCHAR;
        strcpy((char *)p, *pattern);
        //blah blah
    // end setup stuff

    i = cluePrep(STRCT._myPatt);

    return 0;
}


int CALLBACK srchString(char **textstr)
{
    int res,i = Max_record;
    unsigned char c;
    char *textPtr = *textstr;

    STRCT.matched = 0;

    //clean out any non alphanumeric characters while we load the field to be tested
    while ((c = *textPtr++)) if ( isalpha(c) || isdigit(c) || c == ' ' ) STRCT._buffer[i++] = c;
    STRCT._buffer[i] = 0;

    // do the search
    res =  srchMin(STRCT.pattern, STRCT.Misses);

    if (res < 0) return res;

    return STRCT.matched;

}

它所链接的运行时库是:多线程 DLL (/MD)

调用约定是:__cdecl (/Gd)

这就是它变得奇怪的地方。如果我有一个常规的 dot-net 应用程序(代码未显示),它从测试数据库中获取整个记录集,并一次遍历所有记录,调用此 DLL 来调用模糊匹配,我每次都能得到正确的结果时间。

如果我使用上面显示的 SQL 语句对测试数据库使用上面显示的 CLR UDF 应用程序,同时只使用一个线程(单核 VM),我每次都会得到正确的结果。

在多核机器上以 CLR UDF 模式使用此 DLL 时,某些结果是错误的。结果总是有点偏离,但并不始终如一。

292 条记录应该在前两个测试用例中匹配并执行。

在 CLR UDF 多线程情况下,结果将返回 273、284、298、290 等。

C DLL 中的所有存储都在字符数组中。没有使用内存分配。我的理解也是,如果 SQL Server 在多线程模式下使用这个 CLR 应用程序,那么线程都被分配了自己的数据空间。

在将字符串发送到 C DLL 之前,是否需要以某种方式“固定”字符串?我只是不知道如何进行。

4

1 回答 1

1

固定不是您的问题,您的 C 代码不是线程安全的。C 代码使用全局变量STRCT。该全局变量只有一个实例将在所有线程之间共享。每个线程都会STRCT用不同的值更新变量,这会导致错误的结果。

您将不得不重构 C 代码,使其不依赖于全局变量的共享状态。

您可以通过声明__declspec(thread)STRCT来使其工作,这将使用线程本地存储,因此每个线程都可以获得自己的变量副本。但是,这对于每个非托管线程都是唯一的,并且不能保证托管线程和非托管线程之间存在一对一的映射

更好的解决方案是完全摆脱共享状态。您可以通过分配一个SRCH_STRUCTinsetClue并返回该指针来做到这一点。然后每次调用srchString都会将该SRCH_STRUCT指针作为参数。VB.Net 代码只需要像对待 IntPtr 一样对待这个结构,它不需要知道任何关于SRCH_STRUCT. 请注意,您还需要向 DLL 添加一个新函数来释放分配的SRCH_STRUCT.

于 2013-04-02T04:52:33.120 回答