从注册表获取SQL Server实例名称遇iSizeInByte=-1问题求解决
解决SQL Server实例名称获取失败的问题
问题分析
你遇到的GetDataSize返回-1的核心原因是注册表重定向:
- 64位Windows系统中,32位程序访问
HKEY_LOCAL_MACHINE\SOFTWARE时,会被自动重定向到HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node - SQL Server的64位实例信息存储在原路径
HKLM\SOFTWARE\Microsoft\Microsoft SQL Server,32位实例则在重定向后的路径中。若你的程序是32位编译,会找不到目标键值,导致GetDataSize返回-1。
此外你的ReadMultiSz实现存在边界错误:循环for z := 0 to iWCharsInBuffer do会越界访问数组,数组有效索引范围应为0到iWCharsInBuffer-1。
可行解决方法
方法1:修复注册表读取代码(处理64位重定向)
修改代码指定注册表访问的位模式,同时修正ReadMultiSz的循环逻辑:
type TRegistryHelper = class helper for TRegistry public function ReadMultiSz(const name: string; var Strings: TStrings): boolean; end; function TRegistryHelper.ReadMultiSz(const name: string; var Strings: TStrings): boolean; var iSizeInByte: integer; Buffer: array of WChar; iWCharsInBuffer: integer; z: integer; sString: string; begin Result := False; iSizeInByte := GetDataSize(name); if iSizeInByte <= 0 then Exit; SetLength(Buffer, iSizeInByte div sizeof(WChar)); iWCharsInBuffer := ReadBinaryData(name, Buffer[0], iSizeInByte) div sizeof(WChar); if iWCharsInBuffer <= 0 then Exit; sString := ''; // 修正循环边界,避免越界 for z := 0 to iWCharsInBuffer - 1 do begin if Buffer[z] <> #0 then begin sString := sString + Buffer[z]; end else begin if sString <> '' then begin Strings.Append(sString); sString := ''; end; end; end; // 处理最后一个未闭合的字符串(如果存在) if sString <> '' then Strings.Append(sString); Result := True; end; procedure TForm1.Button1Click(Sender: TObject); const cKey = '\SOFTWARE\Microsoft\Microsoft SQL Server'; var Registry: TRegistry; MyList: TStrings; begin Registry := TRegistry.Create; try Registry.RootKey := HKEY_LOCAL_MACHINE; // 设置访问权限,支持读取64位注册表(适用于64位系统) Registry.Access := KEY_READ or KEY_WOW64_64KEY; if Registry.OpenKeyReadOnly(cKey) then try MyList := TStringList.Create(); try if Registry.ReadMultiSz('InstalledInstances', MyList) then ListBox1.Items.Assign(MyList); finally MyList.Free; end; finally Registry.CloseKey; end; finally Registry.Free; end; end;
如果需要读取32位SQL Server实例,将Registry.Access改为KEY_READ or KEY_WOW64_32KEY即可。
方法2:使用SQL Browser服务枚举实例
通过ODBC的SQLBrowseConnectAPI直接枚举SQL Server实例,无需依赖注册表:
uses Windows, SysUtils, Classes, DB; function GetSQLServerInstances(out InstanceList: TStrings): Boolean; var hEnv: SQLHENV; hDbc: SQLHDBC; szConnStrIn, szConnStrOut: array[0..2047] of Char; cbConnStrOut: SQLSMALLINT; ret: SQLRETURN; Pos, Start: Integer; InstanceName: string; begin Result := False; InstanceList.Clear; // 初始化ODBC环境 ret := SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, @hEnv); if ret <> SQL_SUCCESS then Exit; try ret := SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, Pointer(SQL_OV_ODBC3), 0); if ret <> SQL_SUCCESS then Exit; // 分配连接句柄 ret := SQLAllocHandle(SQL_HANDLE_DBC, hEnv, @hDbc); if ret <> SQL_SUCCESS then Exit; try // 设置连接字符串,请求枚举实例 StrPCopy(szConnStrIn, 'DRIVER={SQL Server};'); cbConnStrOut := SizeOf(szConnStrOut); ret := SQLBrowseConnect(hDbc, szConnStrIn, SQL_NTS, szConnStrOut, cbConnStrOut, @cbConnStrOut); if ret = SQL_SUCCESS_WITH_INFO then begin // 解析返回的字符串,提取实例名称 Start := 1; Pos := Pos('SERVER=', szConnStrOut); if Pos > 0 then begin Inc(Pos, 7); Start := Pos; while Pos <= cbConnStrOut do begin if szConnStrOut[Pos] = ';' then Break; Inc(Pos); end; InstanceName := Copy(szConnStrOut, Start, Pos - Start); // 分割多实例(用逗号分隔) InstanceList.CommaText := InstanceName; Result := True; end; end; finally SQLFreeHandle(SQL_HANDLE_DBC, hDbc); end; finally SQLFreeHandle(SQL_HANDLE_ENV, hEnv); end; end; // 调用示例 procedure TForm1.Button2Click(Sender: TObject); var InstanceList: TStrings; begin InstanceList := TStringList.Create; try if GetSQLServerInstances(InstanceList) then ListBox1.Items.Assign(InstanceList); finally InstanceList.Free; end; end;
方法3:同时检查32位和64位注册表路径
若不确定系统位宽,可以同时读取两个路径下的实例信息并合并去重:
- 64位路径:
HKLM\SOFTWARE\Microsoft\Microsoft SQL Server - 32位路径:
HKLM\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server
分别调用ReadMultiSz,将结果合并后去除重复项即可。
内容的提问来源于stack exchange,提问作者Issam
相关产品推荐
相关产品推荐

