【问题标题】:Converting COM Object interface from C to Delphi将 COM 对象接口从 C 转换为 Delphi
【发布时间】:2012-04-03 04:28:46
【问题描述】:

我正在尝试将以下两个接口从 C 头文件转换为 Delphi PAS 单元,但在使用我自己做的那些时遇到了奇怪的问题。我需要帮助了解如何在 Delphi 中实现这些。

来自c头文件的源接口:

interface IParamConfig: IUnknown
{        
    HRESULT SetValue([in] const VARIANT* pValue, [in] BOOL bSetAndCommit);
    HRESULT GetValue([out] VARIANT* pValue, [in] BOOL bGetCommitted);
    HRESULT SetVisible(BOOL bVisible);
    HRESULT GetVisible(BOOL* bVisible);
    HRESULT GetParamID(GUID* pParamID);
    HRESULT GetName([out] BSTR* pName);
    HRESULT GetReadOnly(BOOL* bReadOnly);
    HRESULT GetFullInfo([out] VARIANT* pValue, [out] BSTR* pMeaning, [out] BSTR* pName, [out] BOOL* bReadOnly, [out] BOOL* pVisible);
    HRESULT GetDefValue([out] VARIANT* pValue);
    HRESULT GetValidRange([out] VARIANT* pMinValue, [out] VARIANT* pMaxValue, [out] VARIANT* pDelta);
    HRESULT EnumValidValues([in][out] long* pNumValidValues, [in][out] VARIANT* pValidValues,[in][out] BSTR* pValueNames);
    HRESULT ValueToMeaning([in] const VARIANT* pValue, [out] BSTR* pMeaning);
    HRESULT MeaningToValue([in] const BSTR pMeaning, [out] VARIANT* pValue);
}

interface IModuleConfig: IPersistStream
{
    HRESULT SetValue([in] const GUID* pParamID, [in]  const VARIANT* pValue);
    HRESULT GetValue([in] const GUID* pParamID, [out] VARIANT* pValue);
    HRESULT GetParamConfig([in] const GUID* pParamID, [out] IParamConfig**  pValue);
    HRESULT IsSupported([in] const GUID* pParamID);
    HRESULT SetDefState();
    HRESULT EnumParams([in][out] long* pNumParams, [in][out] GUID* pParamIDs);
    HRESULT CommitChanges([out] VARIANT* pReason);
    HRESULT DeclineChanges();
    HRESULT SaveToRegistry([in] HKEY hKeyRoot, [in] const BSTR pszKeyName, [in] const BOOL bPreferReadable);
    HRESULT LoadFromRegistry([in] HKEY hKeyRoot, [in] const BSTR pszKeyName, [in] const BOOL bPreferReadable);
    HRESULT RegisterForNotifies([in] IModuleCallback* pModuleCallback);
    HRESULT UnregisterFromNotifies([in] IModuleCallback* pModuleCallback);
}

这是我迄今为止的“最大努力”:

type
  TWideStringArray = array[0..1024] of WideString;
  TOleVariantArray = array[0..1024] of OleVariant;
  TGUIDArray = array[0..1024] of TGUID;

  IParamConfig = interface(IUnknown)
    ['{486F726E-5043-49B9-8A0C-C22A2B0524E8}']
    function SetValue(const pValue: OleVariant; bSetAndCommit: BOOL): HRESULT; stdcall;
    function GetValue(out pValue: OleVariant; bGetCommitted: BOOL): HRESULT; stdcall;
    function SetVisible(bVisible: BOOL): HRESULT; stdcall;
    function GetVisible(bVisible: BOOL): HRESULT; stdcall;
    function GetParamID(pParamID: PGUID): HRESULT; stdcall;
    function GetName(out pName: WideString): HRESULT; stdcall;
    function GetReadOnly(bReadOnly: BOOL): HRESULT; stdcall;
    function GetFullInfo(out pValue: OleVariant; out pMeaning: WideString; out pName: WideString; out pReadOnly: BOOL; out pVisible: BOOL): HRESULT; stdcall;
    function GetDefValue(out pValue: OleVariant): HRESULT; stdcall;
    function GetValidRange(out pMinValue: OleVariant; out pMaxValue: OleVariant; out pDelta: OleVariant): HRESULT; stdcall;
    function EnumValidValues(var pNumValidValues: Integer; var pValidValues: TOleVariantArray; var pValueNames: TWideStringArray): HRESULT; stdcall;
    function ValueToMeading(const pValue: OleVariant; out pMeaning: WideString): HRESULT; stdcall;
    function MeaningToValue(const pMeaning: WideString; out pValue: OleVariant): HRESULT; stdcall;
  end;

  IModuleConfig = interface(IPersistStream)
    ['{486F726E-4D43-49B9-8A0C-C22A2B0524E8}']
    function SetValue(const pParamID: TGUID; const pValue: OleVariant): HRESULT; stdcall;
    function GetValue(const pParamID: TGUID; out pValue: OleVariant): HRESULT; stdcall;
    function GetParamConfig(const ParamID: TGUID; out pValue: IParamConfig): HRESULT; stdcall;
    function IsSupported(const pParamID: TGUID): HRESULT; stdcall;
    function SetDefState: HRESULT; stdcall;
    function EnumParams(var pNumParams: Integer; var pParamIDs: TGUIDArray): HRESULT; stdcall;
    function CommitChanges(out pReason: OleVariant): HRESULT; stdcall;
    function DeclineChanges: HRESULT; stdcall;
    function SaveToRegistry(hKeyRoot: HKEY; const pszKeyName: WideString; const bPreferReadable: BOOL): HRESULT; stdcall;
    function LoadFromRegistry(hKeyRoot: HKEY; const pszKeyName: WideString; const bPreferReadable: BOOL): HRESULT; stdcall;
    function RegisterForNotifies(pModuleCallback: IModuleCallback): HRESULT; stdcall;
    function UnregisterFromNotifies(pModuleCallback: IModuleCallback): HRESULT; stdcall;
  end;

这是一些使用 DirectShow 过滤器并尝试在该对象上同时使用 IModuleConfig 和 IParamConfig 接口的示例代码:

procedure TForm10.Button1Click(Sender: TObject);
const
  CLSID_VideoDecoder: TGUID = '{C274FA78-1F05-4EBB-85A7-F89363B9B3EA}';
var
  HR: HRESULT;
  Intf: IUnknown;
  NumParams: Long;
  I: Integer;
  ParamConfig: IParamConfig;
  ParamName: WideString;
  Value: OleVariant;
  ValAsString: String;
  Params: TGUIDArray;
begin
  CoInitializeEx(nil, COINIT_MULTITHREADED);
  try
    HR := CoCreateInstance(CLSID_VideoDecoder, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Intf);
    if Succeeded(HR) then
    begin
      FVideoDecoder := Intf as IBaseFilter;

      if Supports(FVideoDecoder, IID_IModuleConfig) then
      begin
        HR := (FVideoDecoder as IModuleConfig).EnumParams(NumParams, Params);
        if HR = S_OK then
        begin
          for I := 0 to NumParams - 1 do
          begin
            HR := (FVideoDecoder as IModuleConfig).GetParamConfig(Params[I], ParamConfig);
            if HR = S_OK then
            begin
              try
                ParamConfig.GetName(ParamName);
                ParamConfig.GetValue(Value, True);
                try
                  ValAsString := VarToStrDef(Value, 'Error');
                  SL.Add(String(ParamName) + '=' + String(ValAsString)); // <-- ADDING THIS LINE WILL ALWAYS MAKE EnumParams call return S_FALSE = 1
                except
                end;
              finally
                ParamConfig := nil;
              end;
            end;
          end;
        end;
      end;
    end;
  finally
    CoUninitialize;
  end;
end;

使用调试器,我可以看到示例代码将数据检索到 ParamName 和 Value 变量,但是,当我尝试包含代码以将它们存储到字符串列表 (SL) 时,对 EnumParams 的调用将始终返回 S_FALSE (1)而不是 S_OK (0)。如果我注释掉 SL.Add(...) 和 RECOMPILE 行,它将再次起作用。如果我再次包含它并重新编译它不会。这让我相信,由于我对这些接口的错误实现,在某些时候某些东西会弄乱内存,并且包含额外的代码会导致这种情况发生。

我很确定我分配给变量的类型在某种程度上是造成这种情况的罪魁祸首,尤其是 EnumParams 的第二个参数,它应该返回一个 GUID* 数组。我也很不确定 IParamConfig.EnumValidValues 调用也会返回值数组。

我正在使用 Delphi XE2。

非常感谢您对此问题的任何帮助。

【问题讨论】:

    标签: delphi com header translation variant


    【解决方案1】:

    为了明确地回答这个问题,需要有接口的文档。仅仅知道他们的签名是永远不够的信息。如果没有这些文档,我们必须做出有根据的猜测,所以就这样吧。

    让我们首先关注EnumParams

    HRESULT EnumParams([in][out] long* pNumParams, [in][out] GUID* pParamIDs);
    

    注意pNumParams 参数被标记为[in][out]。另一个参数是一组 GUID。很可能您打算通过 pNumParams 参数将数组的长度作为输入传递。这告诉函数可以安全复制多少项目。如果您为pNumParams 传递的值对于整个数组来说是不够的,那么函数将在返回值中指出这一点。当函数返回时,它会将pNumParams 设置为数组的实际长度。很可能您可以将0 称为pNumParamsNULL 用于pParamIDs,并使用它来确定实际需要的数组大小。这是一种非常常见的模式,但您需要阅读文档才能确定。

    现在,由于您在调用EnumParams 之前没有分配给NumParams,因此您正在从堆栈中传递一个随机值。对代码的进一步更改会影响对 EnumParams 的调用方式,这一事实有力地支持了这一假设。

    根据您的实现,假设我的猜测是正确的,您应该在调用EnumParams 之前将NumParams 设置为1025。但是,我可能会避免使用固定大小的数组并分配动态数组。您需要更改EnumParams 的定义以获取指向第一项的指针。我会为界面中的所有数组执行此操作。

    除此之外,我确实注意到您在IParamConfig 中有几个错误。 GetVisible 函数应该是这样的:

    function GetVisible(var bVisible: BOOL): HRESULT; stdcall;
    

    你会发现GetParamID这样写更方便:

    function GetParamID(var pParamID: TGUID): HRESULT; stdcall;
    

    【讨论】:

    • 谢谢大卫!使用您帖子中的信息,我确实设法正确实现了界面。我确实有这个对象的文档,但不幸的是它是受版权保护的,所以我不能在这里发布。
    【解决方案2】:

    为了记录,这是完成的界面:

      IParamConfig = interface(IUnknown)
        ['{486F726E-5043-49B9-8A0C-C22A2B0524E8}']
        function SetValue(const pValue: OleVariant; bSetAndCommit: BOOL): HRESULT; stdcall;
        function GetValue(out pValue: OleVariant; bGetCommitted: BOOL): HRESULT; stdcall;
        function SetVisible(bVisible: BOOL): HRESULT; stdcall;
        function GetVisible(var bVisible: BOOL): HRESULT; stdcall;
        function GetParamID(out pParamID: TGUID): HRESULT; stdcall;
        function GetName(out pName: WideString): HRESULT; stdcall;
        function GetReadOnly(bReadOnly: BOOL): HRESULT; stdcall;
        function GetFullInfo(out pValue: OleVariant; out pMeaning: WideString; out pName: WideString; out pReadOnly: BOOL; out pVisible: BOOL): HRESULT; stdcall;
        function GetDefValue(out pValue: OleVariant): HRESULT; stdcall;
        function GetValidRange(out pMinValue: OleVariant; out pMaxValue: OleVariant; out pDelta: OleVariant): HRESULT; stdcall;
        function EnumValidValues(pNumValidValues: PInteger; pValidValues: POleVariant; pValueNames: PWideString): HRESULT; stdcall;
        function ValueToMeaning(const pValue: OleVariant; out pMeaning: WideString): HRESULT; stdcall;
        function MeaningToValue(const pMeaning: WideString; out pValue: OleVariant): HRESULT; stdcall;
      end;
    
      IModuleConfig = interface(IPersistStream)
        ['{486F726E-4D43-49B9-8A0C-C22A2B0524E8}']
        function SetValue(const pParamID: TGUID; const pValue: OleVariant): HRESULT; stdcall;
        function GetValue(const pParamID: TGUID; out pValue: OleVariant): HRESULT; stdcall;
        function GetParamConfig(const ParamID: TGUID; out pValue: IParamConfig): HRESULT; stdcall;
        function IsSupported(const pParamID: TGUID): HRESULT; stdcall;
        function SetDefState: HRESULT; stdcall;
        function EnumParams(var pNumParams: Integer; pParamIDs: PGUID): HRESULT; stdcall;
        function CommitChanges(out pReason: OleVariant): HRESULT; stdcall;
        function DeclineChanges: HRESULT; stdcall;
        function SaveToRegistry(hKeyRoot: HKEY; const pszKeyName: WideString; const bPreferReadable: BOOL): HRESULT; stdcall;
        function LoadFromRegistry(hKeyRoot: HKEY; const pszKeyName: WideString; const bPreferReadable: BOOL): HRESULT; stdcall;
        function RegisterForNotifies(pModuleCallback: IModuleCallback): HRESULT; stdcall;
        function UnregisterFromNotifies(pModuleCallback: IModuleCallback): HRESULT; stdcall;
      end;
    

    以下代码展示了如何调用和使用接口以及调用EnumParams:

    procedure TForm10.ListAllParameters(Sender: TObject);
    const
      CLSID_VideoDecoder: TGUID = '{C274FA78-1F05-4EBB-85A7-F89363B9B3EA}';
    var
      HR: HRESULT;
      Intf: IUnknown;
      ModuleConfig: IModuleConfig;
      ParamConfig: IParamConfig;
      NumParams: Integer;
      ParamGUIDS: array of TGUID;
      GUID: TGUID;
    begin
      HR := CoCreateInstance(CLSID_VideoDecoder, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Intf);
      try
        if not Succeeded(HR) then Exit;
    
        if Supports(Intf, IID_IModuleConfig) then ModuleConfig := (Intf as IModuleConfig) else Exit;
    
        // Get number of parameters 
        NumParams := 0;
        HR := ModuleConfig.EnumParams(NumParams, nil);
        if HR = S_FALSE then
        begin
          // Set the lenght of the array of TGUIDS to match the number of parameters 
          SetLength(ParamGUIDS, NumParams);
          // Use a pointer to the first TGUID of the array as the parameter to EnumParams 
          HR := ModuleConfig.EnumParams(NumParams, @ParamGUIDS[0]);
          if HR = S_OK then
          begin
            for GUID in ParamGUIDS do Memo1.Lines.Add(GUIDToString(GUID));
          end else Exit;
        end else Exit;
      finally
        ModuleConfig := nil;
        Intf := nil;
      end;
    end;
    

    如果有人发现任何错误(我还没有尝试所有功能),请在这篇文章中发表评论。

    【讨论】:

    • 这仍然受到我在回答中描述的基本错误的影响。您需要在传入 NumParams 之前对其进行初始化。而且您没有更正 GetVisible。此外,对于 NumOfParams 参数,var 参数比传递指针更好。
    • 嗨,大卫,感谢您再次发表评论。我已经更新了我的帖子以包括你的 cmets。我现在明白为什么使用 var 而不是传递指针更好。谢谢!
    猜你喜欢
    • 2011-05-07
    • 2014-01-16
    • 1970-01-01
    • 2011-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-07
    • 2012-08-28
    相关资源
    最近更新 更多