I would use a sub-query to get the values of the function into the result, and then the ROW_NUMBER ranking function, like so:
select
ROW_NUMBER() over (order by t.cnt desc) as RowId, t.*
from
(
SELECT
h.A, hrl.B, dbo.f_GetCount(hrl.A,h.B) as cnt
FROM
dbo.hrl
INNER JOIN dbo.h on h.C = hrl.C
WHERE
(@A IS NULL OR h.A like '%' + @A + '%') AND
(@B IS NULL OR hrl.B = @B)
GROUP BY
hrl.B, h.A
) as t
order by
1
If you wanted only a certain section of results (say, for paging), then you would need another subquery, and then filter on the row number:
select
t.*
from
(
select
ROW_NUMBER() over (order by t.cnt desc) as RowId, t.*
from
(
SELECT
h.A, hrl.B, dbo.f_GetCount(hrl.A,h.B) as cnt
FROM
dbo.hrl
INNER JOIN dbo.h on h.C = hrl.C
WHERE
(@A IS NULL OR h.A like '%' + @A + '%') AND
(@B IS NULL OR hrl.B = @B)
GROUP BY
hrl.B, h.A
) as t
) as t
where
t.RowId between 1 and 10
order by
t.RowId
Note that in this query, you could put ROW_NUMBER anywhere in the select list, since you are no longer reliant on using the "order by 1" syntax for the order by statement.
There is a subtle issue here when calling this query multiple times. It is not guaranteed that the order in which the records are returned are going to be consistent if the number of items in each group is not unique. In order to address this, you have to change the ROW_NUMBER function to order on the fields that make up the group in the count.
In this case, it would be A and B, resulting in:
select
t.*
from
(
select
ROW_NUMBER() over (order by t.cnt desc, t.A, t.B) as RowId, t.*
from
(
SELECT
h.A, hrl.B, dbo.f_GetCount(hrl.A,h.B) as cnt
FROM
dbo.hrl
INNER JOIN dbo.h on h.C = hrl.C
WHERE
(@A IS NULL OR h.A like '%' + @A + '%') AND
(@B IS NULL OR hrl.B = @B)
GROUP BY
hrl.B, h.A
) as t
) as t
where
t.RowId between 1 and 10
order by
t.RowId
This ends up ordering the results consistently between calls when the count of the items between groups is not unique (assuming the same set of data).